Mantid
Loading...
Searching...
No Matches
Statistics.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
7// Includes
9#include "MantidKernel/DllConfig.h"
10#include "MantidKernel/Logger.h"
11
12#include <boost/accumulators/accumulators.hpp>
13#include <boost/accumulators/statistics/max.hpp>
14#include <boost/accumulators/statistics/min.hpp>
15#include <boost/accumulators/statistics/stats.hpp>
16#include <boost/accumulators/statistics/variance.hpp>
17
18#include <algorithm>
19#include <cfloat>
20#include <cmath>
21#include <limits>
22#include <sstream>
23
24namespace Mantid::Kernel {
25namespace {
26Logger logger("Statistics");
27
28void assertMomentIsValid(const int maxMoment) {
29 if (maxMoment < 0) {
30 std::stringstream msg;
31 msg << "moment = " << maxMoment << " is statistically meaningless";
32 throw std::runtime_error(msg.str());
33 }
34}
35} // namespace
36
37using std::string;
38using std::vector;
39
42template <typename Range> Statistics getStatisticsImpl(Range const &data, const unsigned int flags);
43template <typename Range> std::vector<double> getZscoreImpl(Range const &data);
44
46 constexpr double nan = std::numeric_limits<double>::quiet_NaN();
47 minimum = nan;
48 maximum = nan;
49 mean = nan;
50 median = nan;
52}
53
58template <typename Range> double getMedian(Range const &data) {
59 using value_type = std::decay_t<decltype(data[0])>;
60 const size_t size = data.size();
61 if (size == 1)
62 return static_cast<double>(data[0]);
63
64 const bool isSorted = std::is_sorted(data.begin(), data.end());
65 const bool is_even = (size % 2 == 0);
66 if (isSorted) {
67 if (is_even)
68 return (static_cast<double>(data[size / 2 - 1]) + static_cast<double>(data[size / 2])) / 2;
69 else
70 return static_cast<double>(data[size / 2]);
71 } else {
72 std::vector<value_type> tmpSortedData(data.begin(), data.end());
73 std::sort(tmpSortedData.begin(), tmpSortedData.end());
74 if (is_even)
75 return (static_cast<double>(tmpSortedData[size / 2 - 1]) + static_cast<double>(tmpSortedData[size / 2])) / 2;
76 else
77 return static_cast<double>(tmpSortedData[size / 2]);
78 }
79}
80
85template <typename Range> std::vector<double> getZscoreImpl(Range const &data) {
86 std::vector<double> Zscore;
87 if (data.size() < 3) {
88 Zscore.resize(data.size(), 0.);
89 return Zscore;
90 }
92 if (stats.standard_deviation == 0.) {
93 Zscore.resize(data.size(), 0.);
94 return Zscore;
95 }
96 for (auto it = data.begin(); it != data.end(); ++it) {
97 auto tmp = static_cast<double>(*it);
98 // unclear why Zscore is non-negative, was first implemented in #5316
99 Zscore.emplace_back(fabs((stats.mean - tmp) / stats.standard_deviation));
100 }
101 return Zscore;
102}
103
104template <typename TYPE> std::vector<double> getZscore(const vector<TYPE> &data) { return getZscoreImpl(data); }
105
106std::vector<double> getZscore(std::span<double const> data) { return getZscoreImpl(data); }
111template <typename TYPE> std::vector<double> getWeightedZscore(const vector<TYPE> &data, const vector<TYPE> &weights) {
112 std::vector<double> Zscore;
113 if (data.size() < 3) {
114 Zscore.resize(data.size(), 0.);
115 return Zscore;
116 }
117 Statistics stats = getStatistics(data);
118 if (stats.standard_deviation == 0.) {
119 Zscore.resize(data.size(), 0.);
120 return Zscore;
121 }
122 double sumWeights = 0.0;
123 double sumWeightedData = 0.0;
124 double weightedVariance = 0.0;
125 for (size_t it = 0; it != data.size(); ++it) {
126 sumWeights += static_cast<double>(weights[it]);
127 sumWeightedData += static_cast<double>(weights[it] * data[it]);
128 }
129 double weightedMean = sumWeightedData / sumWeights;
130 for (size_t it = 0; it != data.size(); ++it) {
131 weightedVariance += std::pow(static_cast<double>(data[it]) - weightedMean, 2) *
132 std::pow(static_cast<double>(weights[it]) / sumWeights, 2);
133 }
134 for (auto it = data.cbegin(); it != data.cend(); ++it) {
135 Zscore.emplace_back(fabs((static_cast<double>(*it) - weightedMean) / std::sqrt(weightedVariance)));
136 }
137 return Zscore;
138}
144template <typename TYPE> std::vector<double> getModifiedZscore(const vector<TYPE> &data) {
145 if (data.size() < 3) {
146 std::vector<double> Zscore(data.size(), 0.);
147 return Zscore;
148 }
149 std::vector<double> MADvec;
150 double tmp;
151 double median = getMedian(data);
152 for (auto it = data.cbegin(); it != data.cend(); ++it) {
153 tmp = static_cast<double>(*it);
154 MADvec.emplace_back(fabs(tmp - median));
155 }
156 double MAD = getMedian(MADvec);
157 if (MAD == 0.) {
158 std::vector<double> Zscore(data.size(), 0.);
159 return Zscore;
160 }
161 MADvec.clear();
162 std::vector<double> Zscore;
163 for (auto it = data.begin(); it != data.end(); ++it) {
164 tmp = static_cast<double>(*it);
165 Zscore.emplace_back(0.6745 * fabs((tmp - median) / MAD));
166 }
167 return Zscore;
168}
169
176template <typename Range> Statistics getStatisticsImpl(Range const &data, const unsigned int flags) {
177 Statistics statistics;
178 if (data.empty()) { // don't do anything
179 return statistics;
180 }
181 // calculate the mean if this or the stddev is requested
182 const bool stddev = ((flags & StatOptions::UncorrectedStdDev) || (flags & StatOptions::CorrectedStdDev));
183 if (stddev) {
184 using namespace boost::accumulators;
185 accumulator_set<double, stats<tag::min, tag::max, tag::variance>> acc;
186 for (auto &value : data) {
187 acc(static_cast<double>(value));
188 }
189 statistics.minimum = min(acc);
190 statistics.maximum = max(acc);
191 statistics.mean = mean(acc);
192 double var = variance(acc);
193
194 if (flags & StatOptions::CorrectedStdDev) {
195 auto ndofs = static_cast<double>(data.size());
196 var *= ndofs / (ndofs - 1.0);
197 }
198 statistics.standard_deviation = std::sqrt(var);
199
200 } else if (flags & StatOptions::Mean) {
201 using namespace boost::accumulators;
202 accumulator_set<double, stats<tag::mean>> acc;
203 for (auto &value : data) {
204 acc(static_cast<double>(value));
205 }
206 statistics.mean = mean(acc);
207 }
208
209 // calculate the median if requested
210 if (flags & StatOptions::Median) {
211 statistics.median = getMedian(data);
212 }
213
214 return statistics;
215}
216
217template <typename TYPE> Statistics getStatistics(const vector<TYPE> &data, const unsigned int flags) {
218 return getStatisticsImpl(data, flags);
219}
220
221Statistics getStatistics(std::span<double const> data, const unsigned int flags) {
222 return getStatisticsImpl(data, flags);
223}
224
226template <> MANTID_KERNEL_DLL Statistics getStatistics<string>(const vector<string> &data, const unsigned int flags) {
227 UNUSED_ARG(flags);
228 UNUSED_ARG(data);
229 return Statistics(); // default is all nan
230}
231
233template <> MANTID_KERNEL_DLL Statistics getStatistics<bool>(const vector<bool> &data, const unsigned int flags) {
234 UNUSED_ARG(flags);
235 UNUSED_ARG(data);
236 return Statistics(); // default is all nan
237}
238
246Rfactor getRFactor(std::span<double const> obsI, std::span<double const> calI, std::span<double const> obsE) {
247 // 1. Check
248 if (obsI.size() != calI.size() || obsI.size() != obsE.size()) {
249 std::stringstream errss;
250 errss << "GetRFactor() Input Error! Observed Intensity (" << obsI.size() << "), Calculated Intensity ("
251 << calI.size() << ") and Observed Error (" << obsE.size() << ") have different number of elements.";
252 throw std::runtime_error(errss.str());
253 }
254 if (obsI.empty()) {
255 throw std::runtime_error("getRFactor(): the input arrays are empty.");
256 }
257
258 double sumnom = 0;
259 double sumdenom = 0;
260 double sumrpnom = 0;
261 double sumrpdenom = 0;
262
263 size_t numpts = obsI.size();
264 for (size_t i = 0; i < numpts; ++i) {
265 double cal_i = calI[i];
266 double obs_i = obsI[i];
267 double sigma = obsE[i];
268 double weight = 1.0 / (sigma * sigma);
269 double diff = obs_i - cal_i;
270
271 if (weight == weight && weight <= DBL_MAX) {
272 // If weight is not NaN.
273 sumrpnom += fabs(diff);
274 sumrpdenom += fabs(obs_i);
275
276 double tempnom = weight * diff * diff;
277 double tempden = weight * obs_i * obs_i;
278
279 sumnom += tempnom;
280 sumdenom += tempden;
281
282 if (tempnom != tempnom || tempden != tempden) {
283 logger.error() << "***** Error! ****** Data indexed " << i << " is NaN. "
284 << "i = " << i << ": cal = " << calI[i] << ", obs = " << obs_i << ", weight = " << weight
285 << ". \n";
286 }
287 }
288 }
289
290 Rfactor rfactor(0., 0.);
291 rfactor.Rp = (sumrpnom / sumrpdenom);
292 rfactor.Rwp = std::sqrt(sumnom / sumdenom);
293
294 if (rfactor.Rwp != rfactor.Rwp)
295 logger.debug() << "Rwp is NaN. Denominator = " << sumnom << "; Nominator = " << sumdenom << ". \n";
296
297 return rfactor;
298}
299
311template <typename TYPE>
312std::vector<double> getMomentsAboutOrigin(const std::vector<TYPE> &x, const std::vector<TYPE> &y, const int maxMoment) {
313 assertMomentIsValid(maxMoment);
314
315 // densities have the same number of x and y
316 bool isDensity(x.size() == y.size());
317
318 // if it isn't a density then check for histogram
319 if ((!isDensity) && (x.size() != y.size() + 1)) {
320 std::stringstream msg;
321 msg << "length of x (" << x.size() << ") and y (" << y.size() << ")do not match";
322 throw std::out_of_range(msg.str());
323 }
324
325 // initialize a result vector with all zeros
326 std::vector<double> result(std::size_t(maxMoment + 1), 0.);
327
328 // cache the maximum index
329 size_t numPoints = y.size();
330 if (isDensity)
331 numPoints = x.size() - 1;
332
333 // densities are calculated using Newton's method for numerical integration
334 // as backwards as it sounds, the outer loop should be the points rather
335 // than
336 // the moments
337 for (size_t j = 0; j < numPoints; ++j) {
338 // reduce item lookup - and central x for histogram
339 const double xVal = .5 * static_cast<double>(x[j] + x[j + 1]);
340 // this variable will be (x^n)*y
341 auto temp = static_cast<double>(y[j]); // correct for histogram
342 if (isDensity) {
343 const auto xDelta = static_cast<double>(x[j + 1] - x[j]);
344 temp = .5 * (temp + static_cast<double>(y[j + 1])) * xDelta;
345 }
346
347 // accumulate the moments
348 result[0] += temp;
349 for (size_t i = 1; i < result.size(); ++i) {
350 temp *= xVal;
351 result[i] += temp;
352 }
353 }
354
355 return result;
356}
357
369template <typename TYPE>
370std::vector<double> getMomentsAboutMean(const std::vector<TYPE> &x, const std::vector<TYPE> &y, const int maxMoment) {
371 assertMomentIsValid(maxMoment);
372
373 // get the zeroth (integrated value) and first moment (mean)
374 std::vector<double> momentsAboutOrigin = getMomentsAboutOrigin(x, y, 1);
375 const double mean = momentsAboutOrigin[1];
376
377 // initialize a result vector with all zeros
378 std::vector<double> result(std::size_t(maxMoment + 1), 0.);
379 result[0] = momentsAboutOrigin[0];
380
381 // escape early if we need to
382 if (maxMoment == 0)
383 return result;
384
385 // densities have the same number of x and y
386 bool isDensity(x.size() == y.size());
387
388 // cache the maximum index
389 size_t numPoints = y.size();
390 if (isDensity)
391 numPoints = x.size() - 1;
392
393 // densities are calculated using Newton's method for numerical integration
394 // as backwards as it sounds, the outer loop should be the points rather
395 // than
396 // the moments
397 for (size_t j = 0; j < numPoints; ++j) {
398 // central x in histogram with a change of variables - and just change for
399 // density
400 const double xVal = .5 * static_cast<double>(x[j] + x[j + 1]) - mean; // change of variables
401
402 // this variable will be (x^n)*y
403 double temp;
404 if (isDensity) {
405 const auto xDelta = static_cast<double>(x[j + 1] - x[j]);
406 temp = xVal * .5 * static_cast<double>(y[j] + y[j + 1]) * xDelta;
407 } else {
408 temp = xVal * static_cast<double>(y[j]);
409 }
410
411 // accumulate the moment
412 result[1] += temp;
413 for (size_t i = 2; i < result.size(); ++i) {
414 temp *= xVal;
415 result[i] += temp;
416 }
417 }
418
419 return result;
420}
421
422// -------------------------- Macro to instantiation concrete types
423// --------------------------------
424#define INSTANTIATE(TYPE) \
425 template MANTID_KERNEL_DLL Statistics getStatistics<TYPE>(const vector<TYPE> &, const unsigned int); \
426 template MANTID_KERNEL_DLL std::vector<double> getZscore<TYPE>(const vector<TYPE> &); \
427 template MANTID_KERNEL_DLL std::vector<double> getWeightedZscore<TYPE>(const vector<TYPE> &, const vector<TYPE> &); \
428 template MANTID_KERNEL_DLL std::vector<double> getModifiedZscore<TYPE>(const vector<TYPE> &); \
429 template MANTID_KERNEL_DLL std::vector<double> getMomentsAboutOrigin<TYPE>( \
430 const std::vector<TYPE> &x, const std::vector<TYPE> &y, const int maxMoment); \
431 template MANTID_KERNEL_DLL std::vector<double> getMomentsAboutMean<TYPE>( \
432 const std::vector<TYPE> &x, const std::vector<TYPE> &y, const int maxMoment);
433
434// --------------------------- Concrete instantiations
435// ---------------------------------------------
440INSTANTIATE(long long)
441INSTANTIATE(unsigned int)
442INSTANTIATE(unsigned long)
443INSTANTIATE(unsigned long long)
444
445} // namespace Mantid::Kernel
gsl_vector * tmp
double value
The value of the point.
Definition FitMW.cpp:51
#define INSTANTIATE(TYPE)
#define fabs(x)
Definition Matrix.cpp:22
#define UNUSED_ARG(x)
Function arguments are sometimes unused in certain implmentations but are required for documentation ...
Definition System.h:44
std::vector< double > getZscoreImpl(Range const &data)
There are enough special cases in determining the Z score where it useful to put it in a single funct...
std::vector< double > getModifiedZscore(const std::vector< TYPE > &data)
Return the modified Z score values for a dataset.
Statistics getStatistics(const std::vector< TYPE > &data, const unsigned int flags=StatOptions::AllStats)
Return a statistics object for the given data set.
MANTID_KERNEL_DLL Statistics getStatistics< bool >(const vector< bool > &data, const unsigned int flags)
Getting statistics of a boolean array should just give a bunch of NaNs.
Statistics getStatisticsImpl(Range const &data, const unsigned int flags)
Shared implementations, generic over any sized contiguous range, so that the std::vector and std::spa...
std::vector< double > getZscore(const std::vector< TYPE > &data)
Return the Z score values for a dataset.
Rfactor MANTID_KERNEL_DLL getRFactor(std::span< double const > obsI, std::span< double const > calI, std::span< double const > obsE)
Return the R-factors (Rwp) of a diffraction pattern data.
double getMedian(Range const &data)
There are enough special cases in determining the median where it useful to put it in a single functi...
std::vector< double > getMomentsAboutMean(const std::vector< TYPE > &x, const std::vector< TYPE > &y, const int maxMoment=3)
Return the first n-moments of the supplied data.
MANTID_KERNEL_DLL Statistics getStatistics< string >(const vector< string > &data, const unsigned int flags)
Getting statistics of a string array should just give a bunch of NaNs.
std::vector< double > getWeightedZscore(const std::vector< TYPE > &data, const std::vector< TYPE > &weights)
There are enough special cases in determining the Z score where it useful to put it in a single funct...
std::vector< double > getMomentsAboutOrigin(const std::vector< TYPE > &x, const std::vector< TYPE > &y, const int maxMoment=3)
Return the first n-moments of the supplied data.
R factor for powder data analysis.
Definition Statistics.h:66
Simple struct to store statistics.
Definition Statistics.h:36
double mean
Mean value.
Definition Statistics.h:42
double median
Median value.
Definition Statistics.h:44
double minimum
Minimum value.
Definition Statistics.h:38
double maximum
Maximum value.
Definition Statistics.h:40
double standard_deviation
standard_deviation of the values
Definition Statistics.h:46
Statistics()
Default value for everything is nan.