RandLib 2.0.0
Probability distributions, fitting, and heavy-tail tools for C++20
Loading...
Searching...
No Matches
tail.hpp
Go to the documentation of this file.
1#pragma once
2
4#include <randlib/fit/mle.hpp>
5
6#include <algorithm>
7#include <cmath>
8#include <concepts>
9#include <cstddef>
10#include <ranges>
11#include <span>
12#include <stdexcept>
13#include <type_traits>
14#include <utility>
15#include <vector>
16
17namespace randlib
18{
19
21{
22 std::size_t order = 0;
23 double threshold = 0;
24 double estimate = 0;
25};
26
27namespace detail
28{
29
30template <std::floating_point Real>
31std::vector<Real> sortedTailSample(std::span<const Real> sample)
32{
33 if (sample.size() < 2)
34 throw std::invalid_argument("Tail-index estimation requires at least two observations");
35 if (std::ranges::any_of(sample, [](Real value) { return !(value > 0) || !std::isfinite(value); }))
36 throw std::invalid_argument("Tail-index estimation requires positive finite observations");
37 std::vector<Real> sorted(sample.begin(), sample.end());
38 std::ranges::sort(sorted);
39 return sorted;
40}
41
42template <std::floating_point Real>
43double hillFromSorted(std::span<const Real> sorted, std::size_t order)
44{
45 if (order == 0 || order >= sorted.size())
46 throw std::invalid_argument("Hill estimator order should be between one and sample size minus one");
47 const double logThreshold = std::log(static_cast<double>(sorted[sorted.size() - order - 1]));
48 long double sum = 0;
49 for (std::size_t i = sorted.size() - order; i < sorted.size(); ++i)
50 sum += std::log(static_cast<double>(sorted[i])) - logThreshold;
51 return static_cast<double>(sum / order);
52}
53
54} // namespace detail
55
56template <std::floating_point Real>
57double hill_estimator(std::span<const Real> sample, std::size_t order)
58{
59 const auto sorted = detail::sortedTailSample(sample);
60 return detail::hillFromSorted(std::span<const Real>(sorted), order);
61}
62
63template <std::ranges::contiguous_range Range>
64 requires std::floating_point<std::remove_cv_t<std::ranges::range_value_t<Range>>> &&
65 (!std::same_as<std::remove_cvref_t<Range>,
66 std::span<const std::remove_cv_t<std::ranges::range_value_t<Range>>>>)
67double hill_estimator(const Range &sample, std::size_t order)
68{
69 using Real = std::remove_cv_t<std::ranges::range_value_t<Range>>;
70 return hill_estimator(std::span<const Real>(sample), order);
71}
72
73template <std::floating_point Real>
74std::vector<TailIndexEstimate> hill_plot(std::span<const Real> sample, std::size_t maximumOrder = 0)
75{
76 const auto sorted = detail::sortedTailSample(sample);
77 if (maximumOrder == 0)
78 maximumOrder = sorted.size() - 1;
79 if (maximumOrder >= sorted.size())
80 throw std::invalid_argument("Maximum Hill order should be smaller than the sample size");
81
82 std::vector<TailIndexEstimate> result;
83 result.reserve(maximumOrder);
84 for (std::size_t order = 1; order <= maximumOrder; ++order) {
85 result.push_back({order, static_cast<double>(sorted[sorted.size() - order - 1]),
86 detail::hillFromSorted(std::span<const Real>(sorted), order)});
87 }
88 return result;
89}
90
91template <std::ranges::contiguous_range Range>
92 requires std::floating_point<std::remove_cv_t<std::ranges::range_value_t<Range>>> &&
93 (!std::same_as<std::remove_cvref_t<Range>,
94 std::span<const std::remove_cv_t<std::ranges::range_value_t<Range>>>>)
95std::vector<TailIndexEstimate> hill_plot(const Range &sample, std::size_t maximumOrder = 0)
96{
97 using Real = std::remove_cv_t<std::ranges::range_value_t<Range>>;
98 return hill_plot(std::span<const Real>(sample), maximumOrder);
99}
100
101template <std::floating_point Real>
102double pickands_estimator(std::span<const Real> sample, std::size_t order)
103{
104 const auto sorted = detail::sortedTailSample(sample);
105 if (order == 0 || order > sorted.size() / 4)
106 throw std::invalid_argument(
107 "Pickands estimator order should be positive with four times the order at most the sample size");
108 const double upper = static_cast<double>(sorted[sorted.size() - order]);
109 const double middle = static_cast<double>(sorted[sorted.size() - 2 * order]);
110 const double lower = static_cast<double>(sorted[sorted.size() - 4 * order]);
111 const double numerator = upper - middle;
112 const double denominator = middle - lower;
113 if (!(numerator > 0) || !(denominator > 0))
114 throw std::invalid_argument("Pickands estimator requires distinct upper order statistics");
115 return std::log(numerator / denominator) / std::log(2.0);
116}
117
118template <std::ranges::contiguous_range Range>
119 requires std::floating_point<std::remove_cv_t<std::ranges::range_value_t<Range>>> &&
120 (!std::same_as<std::remove_cvref_t<Range>,
121 std::span<const std::remove_cv_t<std::ranges::range_value_t<Range>>>>)
122double pickands_estimator(const Range &sample, std::size_t order)
123{
124 using Real = std::remove_cv_t<std::ranges::range_value_t<Range>>;
125 return pickands_estimator(std::span<const Real>(sample), order);
126}
127
128template <std::floating_point Real>
129FitResult<GeneralizedParetoRand<Real>> fit_peaks_over_threshold(std::span<const Real> sample, Real threshold,
130 FitOptions options = {})
131{
132 if (!std::isfinite(threshold))
133 throw std::invalid_argument("Peaks-over-threshold fitting requires a finite threshold");
134 std::vector<Real> exceedances;
135 exceedances.reserve(sample.size());
136 for (Real value : sample) {
137 if (!std::isfinite(value))
138 throw fit_error(FitErrorCode::WrongSample, "Peaks-over-threshold fitting requires finite observations");
139 if (value > threshold)
140 exceedances.push_back(value - threshold);
141 }
142 auto result = fit<GeneralizedParetoRand<Real>>(std::span<const Real>(exceedances), std::move(options));
143 result.distribution = GeneralizedParetoRand<Real>(result.distribution.GetShape(), result.distribution.GetScale(),
144 static_cast<double>(threshold));
145 return result;
146}
147
148template <std::ranges::contiguous_range Range>
149 requires std::floating_point<std::remove_cv_t<std::ranges::range_value_t<Range>>> &&
150 (!std::same_as<std::remove_cvref_t<Range>,
151 std::span<const std::remove_cv_t<std::ranges::range_value_t<Range>>>>)
152auto fit_peaks_over_threshold(const Range &sample, std::ranges::range_value_t<Range> threshold, FitOptions options = {})
153{
154 using Real = std::remove_cv_t<std::ranges::range_value_t<Range>>;
155 return fit_peaks_over_threshold(std::span<const Real>(sample), static_cast<Real>(threshold), std::move(options));
156}
157
158} // namespace randlib
double hillFromSorted(std::span< const Real > sorted, std::size_t order)
Definition tail.hpp:43
std::vector< Real > sortedTailSample(std::span< const Real > sample)
Definition tail.hpp:31
Real standardNormal(G &g)
Ziggurat algorithm of Marsaglia and Tsang with 256 layers and an explicit sign bit.
Definition variates.hpp:45
Definition bivariate_normal.hpp:10
@ WrongSample
the sample could not have been drawn from this distribution
FitResult< GeneralizedParetoRand< Real > > fit_peaks_over_threshold(std::span< const Real > sample, Real threshold, FitOptions options={})
Definition tail.hpp:129
double pickands_estimator(std::span< const Real > sample, std::size_t order)
Definition tail.hpp:102
double hill_estimator(std::span< const Real > sample, std::size_t order)
Definition tail.hpp:57
std::vector< TailIndexEstimate > hill_plot(std::span< const Real > sample, std::size_t maximumOrder=0)
Definition tail.hpp:74
Definition result.hpp:27
Definition result.hpp:36
Definition tail.hpp:21
double threshold
Definition tail.hpp:23
double estimate
Definition tail.hpp:24
std::size_t order
Definition tail.hpp:22