Skip to main content

ppidest

Plotting Position — Information Divergence framework for parameter estimation of univariate distributions.

The empirical cumulative distribution function of a sample is discretized using plotting positions, and a parametric distribution is fitted by minimizing an information divergence between the empirical gaps and the gaps implied by the candidate CDF.

Installation

Requires Python ≥ 3.13.

pip install ppidest

This installs the ppidest package from PyPI together with its console script (see Command line); the numpy and scipy dependencies are pulled in automatically. For a source checkout you can use pip install . or uv pip install . instead, and uv sync creates the environment and uv run python -m unittest discover -s tests runs the test suite.

Quick start

Fit a GEV model to a small sample using the default (Kullback-Leibler) divergence:

import numpy as np
import ppidest

xs = np.array([0.38, 0.51, 1.44, 2.14])

# Plotting Position – Information Divergence estimator
res = ppidest.find_ppid_min(xs, ppidest.gev_cdf, [0.0, 1.0, 0.25])
print(res.x)   # fitted (loc, scale, shape)   -> [0.5600, 0.3639, 1.0585]
print(res.fun) # minimized divergence         -> 0.17543

# Compute a return level from the fitted model
ppidest.gev_return_level(100.0, res.x)

Comparison across estimation methods (scipy.stats is used only to generate the data / provide the density here):

import scipy.stats

xs = np.sort(scipy.stats.norm.rvs(loc=1.0, scale=2.0, size=8))

# Density/CDF callables must use the (x, par) convention
def norm_pdf(x, par):
    return scipy.stats.norm.pdf(x, loc=par[0], scale=par[1])

# Maximum likelihood from the PDF
ml = ppidest.ML(xs, norm_pdf).find_mle([1.0, 2.0])

# PPID with a different divergence and plot position
ppid = ppidest.PPID(xs, ppidest.normal_cdf, plotposition="Weibull")
res = ppid.find_min_div([1.0, 2.0], divergence="Jensen-Shannon")

# Forward extra positional arguments to the CDF callable
def norm_cdf_given_mu(x, par, mu):
    return ppidest.normal_cdf(x, [mu, par[0]])

ppid = ppidest.PPID(xs, norm_cdf_given_mu)
res = ppid.find_min_div([1.0, 2.0], args=(0.0,))   # scale only, mu fixed

## Background

For a sorted sample `x_(1) <= ... <= x_(n)` a plotting position assigns a
probability to the i-th order statistic:

p_i = (i - alpha) / (n + 1 - alpha - beta)


`alpha` and `beta` are fixed by the chosen scheme:

| Scheme       | alpha | beta |
|--------------|-------|------|
| Weibull      | 0.0   | 0.0  |
| median       | 0.3   | 0.3  |
| Gringorten   | 0.44  | 0.44 |
| Hazen        | 0.5   | 0.5  |

Ties are aggregated so each unique value carries the summed probability
of the order statistics sharing it, and the support is augmented with two
boundary bins at `0` and `1`.  The result is an empirical probability
vector `d*` (the "empirical gaps").  A candidate distribution with
parameters `θ` gives model gaps

dd_j(θ) = F(x_j; θ) - F(x_{j-1}; θ), with x_0 = -inf, x_{k+1} = +inf


The estimator minimizes an information divergence `D(d* || dd(θ))` over
`θ`:

- **Kullback-Leibler** —
  `Σ d* log(d*/dd)`
- **generalized KL** —
  `Σ d* log(d*/dd) - d* + dd`
- **symmetric KL** —
  `Σ (dd - d*) log(dd/d*)`
- **Jensen-Shannon** —
  `½ Σ d* log(d*/m) + dd log(dd/m)`, `m = (d* + dd)/2`
- **beta** (order `β`) —
  `Σ d*(d*^(β-1) - dd^(β-1))/(β-1) - (d*^β - dd^β)/β`
- **power** (index `λ`) —
  `1/(λ(λ+1)) Σ d*[(d*/dd)^λ - 1]`
- **Rényi** (order `α`) —
  `1/(α-1) log Σ d*^α dd^(1-α)`

## Package layout

src/ppidest/ distributions.py distribution cdf/pdf/quantile functions (normal, GEV, three-parameter Weibull) plotting.py plotting-position schemes and empirical gaps divergences.py information divergences between probability vectors estimators.py ML, PPID estimators init.py public API and legacy calc_* aliases


### Distributions

All distribution functions share the signature `f(x, par)` with
`par = (loc, scale, shape)` (normal and Weibull parameters differ, see
their docstrings):

- `normal_cdf`, `normal_pdf`
- `gev_cdf`, `gev_pdf`, `gev_quantile`, `gev_return_level`
  (GEV shape `ξ` uses the convention `ξ > 0` → heavy tail)
- `weibull_cdf`, `weibull_pdf`

### Divergences

`ppidest.kl_divergence`, `kl_generalized_divergence`,
`kl_symmetric_divergence`, `jensen_shannon_divergence`,
`beta_divergence(das, ddas, beta)`, `power_divergence(das, ddas, lmbd)`,
`renyi_divergence(das, ddas, alpha)` — all take the empirical gaps
`das` and model gaps `ddas` as 1-D arrays.

### Estimators

| Estimator | Objective | Fit method |
|-----------|-----------|------------|
| `ML(xs, pdf)`     | negative log-likelihood             | `find_mle`    |
| `PPID(xs, cdf)`   | chosen information divergence       | `find_min_div` |

Every `find_*` method mirrors the keyword arguments of
`scipy.optimize.minimize` (`method`, `jac`, `hess`, `bounds`, ...).
`PPID.find_min_div` accepts `divergence` and, for the parametric
divergences, `pdiv`.  The parametric divergence arguments are passed as
singular floats (the pre-1.0 API required a length-one list).

The legacy `calc_gev_cdf`, ... names are exported from the package root as
aliases for backward compatibility, e.g. `ppidest.calc_gev_cdf` is the
same object as `ppidest.gev_cdf`.

## Notes on optimization

Nelder–Mead is the default solver because the objective only needs
CDF evaluation.  During the search the parameters may leave the support
of the distribution, producing `NaN`; these evaluations are harmless and
the corresponding warnings are suppressed inside the distribution and
divergence functions.  Careful initial values (e.g. from the method of
moments) are recommended for the scale and shape parameters.

Release files for ppidest 0.8.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ppidest 0.8.0
File Size Uploaded
ppidest-0.8.0.tar.gz 11.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ppidest 0.8.0
File Interpreter ABI Platform
ppidest-0.8.0-py3-none-any.whl Python 3 none any Details

Total release size: 26.7 kB

Release files / ppidest-0.8.0.tar.gz

Download URL ppidest-0.8.0.tar.gz
Size 11.9 kB
Tags Source
SHA-256 checksum
How to use checksums
5facecc492a0acb6f1a5e5033e7c7ebd7f85c73f0139e109026ef5e88e5fe4f6
BLAKE2b-256 checksum
How to use checksums
8a52d0813178d1b52ecf2c69d9c055fb8e3a090d10e348d5e944b71f3c47ea82
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / ppidest-0.8.0-py3-none-any.whl

Download URL ppidest-0.8.0-py3-none-any.whl
Size 14.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
729cb7b7a29e4ad2eda65d10b6b1b6a71a85a060b49eec6180d6da9508ba8c9a
BLAKE2b-256 checksum
How to use checksums
7ac9f1a55c65cf4610cef52902bb7e96c7074d21c110b69b13f31015160fd052
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.8.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page