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