Threshold Selection¶
threshold
¶
Threshold selection for peaks-over-threshold analysis.
Choosing the threshold is the decision that dominates a peaks-over-threshold analysis, and no rule settles it. Too low and observations from the body of the distribution contaminate the fit, biasing the result. Too high and few exceedances remain, so the estimate is noisy. Everything here is a tool for making that trade-off visible rather than a substitute for looking.
Two diagnostics and one automatic rule:
- :func:
mean_residual_life-- the mean excess is linear in the threshold above a valid one, so the plot should straighten out. - :func:
parameter_stability-- the shape and the modified scale are constant above a valid threshold, so the plot should flatten. - :func:
select_threshold-- the lowest threshold at which a goodness-of-fit test does not reject the generalized Pareto model.
Read the first two before trusting the third.
mean_residual_life
¶
Mean excess over a range of thresholds, with confidence intervals.
If the generalized Pareto model holds above some threshold, the mean excess is linear in the threshold from there upwards. So the plot should be curved at low thresholds and straighten out once the model applies, and the point where it straightens is a candidate threshold.
The right-hand end is always noisy, because it rests on a handful of observations. Judge linearity from the region where the intervals are still narrow.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Sequence[float]
|
Sample values. |
required |
thresholds
|
Sequence[float] | None
|
Candidate thresholds. Defaults to forty spanning the 70th to 99th percentile. |
None
|
level
|
float
|
Confidence level for the interval around each mean excess. |
0.95
|
Returns:
| Type | Description |
|---|---|
list[dict[str, Any]]
|
One dictionary per threshold with |
list[dict[str, Any]]
|
|
list[dict[str, Any]]
|
with fewer than two exceedances are omitted. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the sample is too small or |
Examples:
>>> from heavytails import Pareto
>>> data = Pareto(alpha=2.0, xm=1.0).rvs(10000, seed=1)
>>> points = mean_residual_life(data)
>>> points[0]["mean_excess"] > 0
True
Source code in heavytails/threshold.py
parameter_stability
¶
Fitted generalized Pareto parameters across a range of thresholds.
Above a valid threshold the shape is the same at every higher threshold,
and the scale grows linearly: sigma_u = sigma_0 + xi * (u - u_0). The
modified scale sigma_u - xi * u removes that growth and is
therefore constant. Both should flatten into a plateau above a valid
threshold, and the start of the plateau is a candidate.
The plot degrades badly at the top, where two parameters are being fitted to a few dozen points. That is not a defect in the fit; it is the variance half of the trade-off, and it is why this is read as a plot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Sequence[float]
|
Sample values. |
required |
thresholds
|
Sequence[float] | None
|
Candidate thresholds. Defaults to forty spanning the 70th to 99th percentile. |
None
|
min_exceedances
|
int
|
Skip thresholds leaving fewer exceedances than this. Fitting two parameters to fewer is not informative. |
30
|
Returns:
| Type | Description |
|---|---|
list[dict[str, Any]]
|
One dictionary per threshold with |
list[dict[str, Any]]
|
|
list[dict[str, Any]]
|
are omitted. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the sample is too small. |
Examples:
>>> from heavytails import Pareto
>>> data = Pareto(alpha=2.0, xm=1.0).rvs(20000, seed=1)
>>> points = parameter_stability(data)
>>> abs(points[0]["xi"] - 0.5) < 0.1
True
Source code in heavytails/threshold.py
return_level
¶
Return level: the value exceeded once every period observations.
Fits the generalized Pareto to the excesses above threshold and
evaluates
x_T = u + (sigma / xi) * ((T * rate) ** xi - 1)
where rate is the observed exceedance rate. This is the calculation
behind a "1-in-100-year" figure.
Because x_T grows like T ** xi, a small error in the shape becomes
a large error in the return level, so the interval matters more than
usual. It is obtained by resampling the data, refitting, and taking
percentiles; that captures sampling variability but not the error from
choosing the threshold, nor any doubt about whether the model applies at
all.
Measured coverage of a nominal 95% interval is about 0.88 for a Pareto tail
at n = 20000, falling to roughly 0.76 at n = 8000. Treat the
interval as a lower bound on the uncertainty rather than a calibrated
statement, particularly on small samples.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Sequence[float]
|
Sample values. |
required |
threshold
|
float
|
The threshold to fit above. |
required |
period
|
float
|
Return period |
required |
level
|
float
|
Confidence level for the interval. |
0.95
|
n_bootstrap
|
int
|
Resamples for the interval. |
200
|
seed
|
int | None
|
Seed, for reproducibility. |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dictionary with |
dict[str, Any]
|
|
dict[str, Any]
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If there are too few exceedances, the period is too short, or the arguments are out of range. |
Examples:
>>> from heavytails import Pareto
>>> data = Pareto(alpha=2.0, xm=1.0).rvs(20000, seed=1)
>>> result = return_level(data, threshold=10.0, period=1000, n_bootstrap=20)
>>> result["lower"] < result["return_level"] < result["upper"]
True
Source code in heavytails/threshold.py
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 | |
select_threshold
¶
Choose the lowest threshold at which the generalized Pareto model fits.
Walks the candidates upwards, fits the model to the excesses at each, and tests the fit with the Anderson-Darling statistic. The first threshold the test does not reject is returned, since a lower threshold keeps more data and so gives a less variable estimate.
.. warning::
The p-values are conservative, because the parameters are estimated
from the same excesses being tested. A conservative test rejects less
often than its nominal level, so this rule tends to select a threshold
that is too low. Treat the answer as a starting point and check it
against :func:mean_residual_life and :func:parameter_stability.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Sequence[float]
|
Sample values. |
required |
thresholds
|
Sequence[float] | None
|
Candidates, tried in increasing order. Defaults to forty spanning the 70th to 99th percentile. |
None
|
alpha_level
|
float
|
Significance level for the goodness-of-fit test. |
0.05
|
min_exceedances
|
int
|
Skip thresholds leaving fewer exceedances than this. |
50
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dictionary with |
dict[str, Any]
|
|
dict[str, Any]
|
candidate passes, |
dict[str, Any]
|
records every attempt, so the failure is inspectable. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the sample is too small or |
Examples:
>>> from heavytails import Pareto
>>> data = Pareto(alpha=2.0, xm=1.0).rvs(20000, seed=1)
>>> result = select_threshold(data)
>>> result["threshold"] is not None
True
Source code in heavytails/threshold.py
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 | |