ppidest 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ppidest-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,195 @@
1
+ Metadata-Version: 2.4
2
+ Name: ppidest
3
+ Version: 0.1.0
4
+ Summary: Plotting Position-Information Divergence framework for parameter estimation of univariate distributions
5
+ Keywords: statistics,parameter-estimation,information-divergence,plotting-positions,extremes
6
+ Author: Takuya Kawanishi
7
+ Author-email: Takuya Kawanishi <takuya@exanalytics.sakura.ne.jp>
8
+ License-Expression: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
15
+ Requires-Dist: numpy>=2.5.3
16
+ Requires-Dist: scipy>=1.18.1
17
+ Requires-Python: >=3.13
18
+ Project-URL: Repository, https://codeberg.org/takuya_kawanishi/ppidest
19
+ Project-URL: Homepage, https://codeberg.org/takuya_kawanishi/ppidest
20
+ Description-Content-Type: text/markdown
21
+
22
+ # ppidest
23
+
24
+ **Plotting Position — Information Divergence** framework for parameter
25
+ estimation of univariate distributions.
26
+
27
+ The empirical cumulative distribution function of a sample is discretized
28
+ using *plotting positions*, and a parametric distribution is fitted by
29
+ minimizing an *information divergence* between the empirical gaps and the
30
+ gaps implied by the candidate CDF.
31
+
32
+ ## Installation
33
+
34
+ Requires Python ≥ 3.13.
35
+
36
+ ```bash
37
+ uv sync # create the environment and install the package
38
+ uv run python -m unittest discover -s tests # run the test suite
39
+ ```
40
+
41
+ Dependencies: `numpy`, `scipy`. The package is also installable from a
42
+ source checkout with `pip install .` or `uv pip install .`, and the
43
+ distributions on PyPI ship a `ppidest` console script (see
44
+ [Command line](#command-line)).
45
+
46
+ ## Quick start
47
+
48
+ Fit a GEV model to a small sample using the default (Kullback-Leibler)
49
+ divergence:
50
+
51
+ ```python
52
+ import numpy as np
53
+ import ppidest
54
+
55
+ xs = np.array([0.38, 0.51, 1.44, 2.14])
56
+
57
+ # Plotting Position – Information Divergence estimator
58
+ res = ppidest.find_ppid_min(xs, ppidest.gev_cdf, [0.0, 1.0, 0.25])
59
+ print(res.x) # fitted (loc, scale, shape) -> [0.5600, 0.3639, 1.0585]
60
+ print(res.fun) # minimized divergence -> 0.17543
61
+
62
+ # Compute a return level from the fitted model
63
+ ppidest.gev_return_level(100.0, res.x)
64
+ ```
65
+
66
+ Comparison across estimation methods (`scipy.stats` is used only to
67
+ generate the data / provide the density here):
68
+
69
+ ```python
70
+ import scipy.stats
71
+
72
+ xs = np.sort(scipy.stats.norm.rvs(loc=1.0, scale=2.0, size=8))
73
+
74
+ # Density/CDF callables must use the (x, par) convention
75
+ def norm_pdf(x, par):
76
+ return scipy.stats.norm.pdf(x, loc=par[0], scale=par[1])
77
+
78
+ # Maximum likelihood from the PDF
79
+ ml = ppidest.ML(xs, norm_pdf).find_mle([1.0, 2.0])
80
+
81
+ # PPID with a different divergence and plot position
82
+ ppid = ppidest.PPID(xs, ppidest.normal_cdf, plotposition="Weibull")
83
+ res = ppid.find_min_div([1.0, 2.0], divergence="Jensen-Shannon")
84
+
85
+ # Forward extra positional arguments to the CDF callable
86
+ def norm_cdf_given_mu(x, par, mu):
87
+ return ppidest.normal_cdf(x, [mu, par[0]])
88
+
89
+ ppid = ppidest.PPID(xs, norm_cdf_given_mu)
90
+ res = ppid.find_min_div([1.0, 2.0], args=(0.0,)) # scale only, mu fixed
91
+ ```
92
+ ```
93
+
94
+ ## Background
95
+
96
+ For a sorted sample `x_(1) <= ... <= x_(n)` a plotting position assigns a
97
+ probability to the i-th order statistic:
98
+
99
+ ```
100
+ p_i = (i - alpha) / (n + 1 - alpha - beta)
101
+ ```
102
+
103
+ `alpha` and `beta` are fixed by the chosen scheme:
104
+
105
+ | Scheme | alpha | beta |
106
+ |--------------|-------|------|
107
+ | Weibull | 0.0 | 0.0 |
108
+ | median | 0.3 | 0.3 |
109
+ | Gringorten | 0.44 | 0.44 |
110
+ | Hazen | 0.5 | 0.5 |
111
+
112
+ Ties are aggregated so each unique value carries the summed probability
113
+ of the order statistics sharing it, and the support is augmented with two
114
+ boundary bins at `0` and `1`. The result is an empirical probability
115
+ vector `d*` (the "empirical gaps"). A candidate distribution with
116
+ parameters `θ` gives model gaps
117
+
118
+ ```
119
+ dd_j(θ) = F(x_j; θ) - F(x_{j-1}; θ), with x_0 = -inf, x_{k+1} = +inf
120
+ ```
121
+
122
+ The estimator minimizes an information divergence `D(d* || dd(θ))` over
123
+ `θ`:
124
+
125
+ - **Kullback-Leibler** —
126
+ `Σ d* log(d*/dd)`
127
+ - **generalized KL** —
128
+ `Σ d* log(d*/dd) - d* + dd`
129
+ - **symmetric KL** —
130
+ `Σ (dd - d*) log(dd/d*)`
131
+ - **Jensen-Shannon** —
132
+ `½ Σ d* log(d*/m) + dd log(dd/m)`, `m = (d* + dd)/2`
133
+ - **beta** (order `β`) —
134
+ `Σ d*(d*^(β-1) - dd^(β-1))/(β-1) - (d*^β - dd^β)/β`
135
+ - **power** (index `λ`) —
136
+ `1/(λ(λ+1)) Σ d*[(d*/dd)^λ - 1]`
137
+ - **Rényi** (order `α`) —
138
+ `1/(α-1) log Σ d*^α dd^(1-α)`
139
+
140
+ ## Package layout
141
+
142
+ ```
143
+ src/ppidest/
144
+ distributions.py distribution cdf/pdf/quantile functions
145
+ (normal, GEV, three-parameter Weibull)
146
+ plotting.py plotting-position schemes and empirical gaps
147
+ divergences.py information divergences between probability vectors
148
+ estimators.py ML, PPID estimators
149
+ __init__.py public API and legacy calc_* aliases
150
+ ```
151
+
152
+ ### Distributions
153
+
154
+ All distribution functions share the signature `f(x, par)` with
155
+ `par = (loc, scale, shape)` (normal and Weibull parameters differ, see
156
+ their docstrings):
157
+
158
+ - `normal_cdf`, `normal_pdf`
159
+ - `gev_cdf`, `gev_pdf`, `gev_quantile`, `gev_return_level`
160
+ (GEV shape `ξ` uses the convention `ξ > 0` → heavy tail)
161
+ - `weibull_cdf`, `weibull_pdf`
162
+
163
+ ### Divergences
164
+
165
+ `ppidest.kl_divergence`, `kl_generalized_divergence`,
166
+ `kl_symmetric_divergence`, `jensen_shannon_divergence`,
167
+ `beta_divergence(das, ddas, beta)`, `power_divergence(das, ddas, lmbd)`,
168
+ `renyi_divergence(das, ddas, alpha)` — all take the empirical gaps
169
+ `das` and model gaps `ddas` as 1-D arrays.
170
+
171
+ ### Estimators
172
+
173
+ | Estimator | Objective | Fit method |
174
+ |-----------|-----------|------------|
175
+ | `ML(xs, pdf)` | negative log-likelihood | `find_mle` |
176
+ | `PPID(xs, cdf)` | chosen information divergence | `find_min_div` |
177
+
178
+ Every `find_*` method mirrors the keyword arguments of
179
+ `scipy.optimize.minimize` (`method`, `jac`, `hess`, `bounds`, ...).
180
+ `PPID.find_min_div` accepts `divergence` and, for the parametric
181
+ divergences, `pdiv`. The parametric divergence arguments are passed as
182
+ singular floats (the pre-1.0 API required a length-one list).
183
+
184
+ The legacy `calc_gev_cdf`, ... names are exported from the package root as
185
+ aliases for backward compatibility, e.g. `ppidest.calc_gev_cdf` is the
186
+ same object as `ppidest.gev_cdf`.
187
+
188
+ ## Notes on optimization
189
+
190
+ Nelder–Mead is the default solver because the objective only needs
191
+ CDF evaluation. During the search the parameters may leave the support
192
+ of the distribution, producing `NaN`; these evaluations are harmless and
193
+ the corresponding warnings are suppressed inside the distribution and
194
+ divergence functions. Careful initial values (e.g. from the method of
195
+ moments) are recommended for the scale and shape parameters.
@@ -0,0 +1,174 @@
1
+ # ppidest
2
+
3
+ **Plotting Position — Information Divergence** framework for parameter
4
+ estimation of univariate distributions.
5
+
6
+ The empirical cumulative distribution function of a sample is discretized
7
+ using *plotting positions*, and a parametric distribution is fitted by
8
+ minimizing an *information divergence* between the empirical gaps and the
9
+ gaps implied by the candidate CDF.
10
+
11
+ ## Installation
12
+
13
+ Requires Python ≥ 3.13.
14
+
15
+ ```bash
16
+ uv sync # create the environment and install the package
17
+ uv run python -m unittest discover -s tests # run the test suite
18
+ ```
19
+
20
+ Dependencies: `numpy`, `scipy`. The package is also installable from a
21
+ source checkout with `pip install .` or `uv pip install .`, and the
22
+ distributions on PyPI ship a `ppidest` console script (see
23
+ [Command line](#command-line)).
24
+
25
+ ## Quick start
26
+
27
+ Fit a GEV model to a small sample using the default (Kullback-Leibler)
28
+ divergence:
29
+
30
+ ```python
31
+ import numpy as np
32
+ import ppidest
33
+
34
+ xs = np.array([0.38, 0.51, 1.44, 2.14])
35
+
36
+ # Plotting Position – Information Divergence estimator
37
+ res = ppidest.find_ppid_min(xs, ppidest.gev_cdf, [0.0, 1.0, 0.25])
38
+ print(res.x) # fitted (loc, scale, shape) -> [0.5600, 0.3639, 1.0585]
39
+ print(res.fun) # minimized divergence -> 0.17543
40
+
41
+ # Compute a return level from the fitted model
42
+ ppidest.gev_return_level(100.0, res.x)
43
+ ```
44
+
45
+ Comparison across estimation methods (`scipy.stats` is used only to
46
+ generate the data / provide the density here):
47
+
48
+ ```python
49
+ import scipy.stats
50
+
51
+ xs = np.sort(scipy.stats.norm.rvs(loc=1.0, scale=2.0, size=8))
52
+
53
+ # Density/CDF callables must use the (x, par) convention
54
+ def norm_pdf(x, par):
55
+ return scipy.stats.norm.pdf(x, loc=par[0], scale=par[1])
56
+
57
+ # Maximum likelihood from the PDF
58
+ ml = ppidest.ML(xs, norm_pdf).find_mle([1.0, 2.0])
59
+
60
+ # PPID with a different divergence and plot position
61
+ ppid = ppidest.PPID(xs, ppidest.normal_cdf, plotposition="Weibull")
62
+ res = ppid.find_min_div([1.0, 2.0], divergence="Jensen-Shannon")
63
+
64
+ # Forward extra positional arguments to the CDF callable
65
+ def norm_cdf_given_mu(x, par, mu):
66
+ return ppidest.normal_cdf(x, [mu, par[0]])
67
+
68
+ ppid = ppidest.PPID(xs, norm_cdf_given_mu)
69
+ res = ppid.find_min_div([1.0, 2.0], args=(0.0,)) # scale only, mu fixed
70
+ ```
71
+ ```
72
+
73
+ ## Background
74
+
75
+ For a sorted sample `x_(1) <= ... <= x_(n)` a plotting position assigns a
76
+ probability to the i-th order statistic:
77
+
78
+ ```
79
+ p_i = (i - alpha) / (n + 1 - alpha - beta)
80
+ ```
81
+
82
+ `alpha` and `beta` are fixed by the chosen scheme:
83
+
84
+ | Scheme | alpha | beta |
85
+ |--------------|-------|------|
86
+ | Weibull | 0.0 | 0.0 |
87
+ | median | 0.3 | 0.3 |
88
+ | Gringorten | 0.44 | 0.44 |
89
+ | Hazen | 0.5 | 0.5 |
90
+
91
+ Ties are aggregated so each unique value carries the summed probability
92
+ of the order statistics sharing it, and the support is augmented with two
93
+ boundary bins at `0` and `1`. The result is an empirical probability
94
+ vector `d*` (the "empirical gaps"). A candidate distribution with
95
+ parameters `θ` gives model gaps
96
+
97
+ ```
98
+ dd_j(θ) = F(x_j; θ) - F(x_{j-1}; θ), with x_0 = -inf, x_{k+1} = +inf
99
+ ```
100
+
101
+ The estimator minimizes an information divergence `D(d* || dd(θ))` over
102
+ `θ`:
103
+
104
+ - **Kullback-Leibler** —
105
+ `Σ d* log(d*/dd)`
106
+ - **generalized KL** —
107
+ `Σ d* log(d*/dd) - d* + dd`
108
+ - **symmetric KL** —
109
+ `Σ (dd - d*) log(dd/d*)`
110
+ - **Jensen-Shannon** —
111
+ `½ Σ d* log(d*/m) + dd log(dd/m)`, `m = (d* + dd)/2`
112
+ - **beta** (order `β`) —
113
+ `Σ d*(d*^(β-1) - dd^(β-1))/(β-1) - (d*^β - dd^β)/β`
114
+ - **power** (index `λ`) —
115
+ `1/(λ(λ+1)) Σ d*[(d*/dd)^λ - 1]`
116
+ - **Rényi** (order `α`) —
117
+ `1/(α-1) log Σ d*^α dd^(1-α)`
118
+
119
+ ## Package layout
120
+
121
+ ```
122
+ src/ppidest/
123
+ distributions.py distribution cdf/pdf/quantile functions
124
+ (normal, GEV, three-parameter Weibull)
125
+ plotting.py plotting-position schemes and empirical gaps
126
+ divergences.py information divergences between probability vectors
127
+ estimators.py ML, PPID estimators
128
+ __init__.py public API and legacy calc_* aliases
129
+ ```
130
+
131
+ ### Distributions
132
+
133
+ All distribution functions share the signature `f(x, par)` with
134
+ `par = (loc, scale, shape)` (normal and Weibull parameters differ, see
135
+ their docstrings):
136
+
137
+ - `normal_cdf`, `normal_pdf`
138
+ - `gev_cdf`, `gev_pdf`, `gev_quantile`, `gev_return_level`
139
+ (GEV shape `ξ` uses the convention `ξ > 0` → heavy tail)
140
+ - `weibull_cdf`, `weibull_pdf`
141
+
142
+ ### Divergences
143
+
144
+ `ppidest.kl_divergence`, `kl_generalized_divergence`,
145
+ `kl_symmetric_divergence`, `jensen_shannon_divergence`,
146
+ `beta_divergence(das, ddas, beta)`, `power_divergence(das, ddas, lmbd)`,
147
+ `renyi_divergence(das, ddas, alpha)` — all take the empirical gaps
148
+ `das` and model gaps `ddas` as 1-D arrays.
149
+
150
+ ### Estimators
151
+
152
+ | Estimator | Objective | Fit method |
153
+ |-----------|-----------|------------|
154
+ | `ML(xs, pdf)` | negative log-likelihood | `find_mle` |
155
+ | `PPID(xs, cdf)` | chosen information divergence | `find_min_div` |
156
+
157
+ Every `find_*` method mirrors the keyword arguments of
158
+ `scipy.optimize.minimize` (`method`, `jac`, `hess`, `bounds`, ...).
159
+ `PPID.find_min_div` accepts `divergence` and, for the parametric
160
+ divergences, `pdiv`. The parametric divergence arguments are passed as
161
+ singular floats (the pre-1.0 API required a length-one list).
162
+
163
+ The legacy `calc_gev_cdf`, ... names are exported from the package root as
164
+ aliases for backward compatibility, e.g. `ppidest.calc_gev_cdf` is the
165
+ same object as `ppidest.gev_cdf`.
166
+
167
+ ## Notes on optimization
168
+
169
+ Nelder–Mead is the default solver because the objective only needs
170
+ CDF evaluation. During the search the parameters may leave the support
171
+ of the distribution, producing `NaN`; these evaluations are harmless and
172
+ the corresponding warnings are suppressed inside the distribution and
173
+ divergence functions. Careful initial values (e.g. from the method of
174
+ moments) are recommended for the scale and shape parameters.
@@ -0,0 +1,41 @@
1
+ [project]
2
+ name = "ppidest"
3
+ version = "0.1.0"
4
+ description = "Plotting Position-Information Divergence framework for parameter estimation of univariate distributions"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ license = "MIT"
8
+ keywords = [
9
+ "statistics",
10
+ "parameter-estimation",
11
+ "information-divergence",
12
+ "plotting-positions",
13
+ "extremes",
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Science/Research",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Topic :: Scientific/Engineering :: Mathematics",
22
+ ]
23
+ dependencies = [
24
+ "numpy>=2.5.3",
25
+ "scipy>=1.18.1",
26
+ ]
27
+
28
+ [[project.authors]]
29
+ name = "Takuya Kawanishi"
30
+ email = "takuya@exanalytics.sakura.ne.jp"
31
+
32
+ [project.scripts]
33
+ ppidest = "ppidest:main"
34
+
35
+ [project.urls]
36
+ Repository = "https://codeberg.org/takuya_kawanishi/ppidest"
37
+ Homepage = "https://codeberg.org/takuya_kawanishi/ppidest"
38
+
39
+ [build-system]
40
+ requires = ["uv_build>=0.12.10,<0.13.0"]
41
+ build-backend = "uv_build"
@@ -0,0 +1,34 @@
1
+ [project]
2
+ name = "ppidest"
3
+ version = "0.1.0"
4
+ description = "Plotting Position-Information Divergence framework for parameter estimation of univariate distributions"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ license = "MIT"
8
+ authors = [
9
+ { name = "Takuya Kawanishi", email = "takuya@exanalytics.sakura.ne.jp" },
10
+ ]
11
+ keywords = ["statistics", "parameter-estimation", "information-divergence", "plotting-positions", "extremes"]
12
+ classifiers = [
13
+ "Development Status :: 4 - Beta",
14
+ "Intended Audience :: Science/Research",
15
+ "Operating System :: OS Independent",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.13",
18
+ "Topic :: Scientific/Engineering :: Mathematics",
19
+ ]
20
+ dependencies = [
21
+ "numpy>=2.5.3",
22
+ "scipy>=1.18.1",
23
+ ]
24
+
25
+ [project.scripts]
26
+ ppidest = "ppidest:main"
27
+
28
+ [project.urls]
29
+ Repository = "https://codeberg.org/takuya_kawanishi/ppidest"
30
+ Homepage = "https://codeberg.org/takuya_kawanishi/ppidest"
31
+
32
+ [build-system]
33
+ requires = ["uv_build>=0.12.10,<0.13.0"]
34
+ build-backend = "uv_build"
@@ -0,0 +1,199 @@
1
+ """Plotting Position-Information Divergence (PPID) framework.
2
+
3
+ Parameter estimation for univariate distributions based on discretizing
4
+ the cumulative distribution function with plotting positions and
5
+ minimizing information divergences against the empirical gaps.
6
+
7
+ Public API
8
+ ----------
9
+ - Distributions: :func:`normal_cdf`, :func:`normal_pdf`, :func:`gev_cdf`,
10
+ :func:`gev_pdf`, :func:`gev_quantile`, :func:`gev_return_level`,
11
+ :func:`weibull_cdf`, :func:`weibull_pdf`.
12
+ - Plotting positions: :func:`plotting_positions`.
13
+ - Divergences: :func:`kl_divergence`, :func:`kl_generalized_divergence`,
14
+ :func:`kl_symmetric_divergence`, :func:`jensen_shannon_divergence`,
15
+ :func:`beta_divergence`, :func:`power_divergence`,
16
+ :func:`renyi_divergence`.
17
+ - Estimators: :class:`ML`, :class:`PPID`
18
+ and the convenience wrapper :func:`find_ppid_min`.
19
+
20
+ The ``calc_*`` names from earlier releases are re-exported here as
21
+ backward-compatible aliases (e.g. :data:`calc_gev_cdf`).
22
+
23
+ Command line
24
+ ------------
25
+ The ``ppidest`` console script fits a distribution to sample values given
26
+ on the command line::
27
+
28
+ ppidest 0.38 0.51 1.44 2.14
29
+ ppidest 0.38 0.51 1.44 2.14 --distribution normal
30
+ ppidest 0.38 0.51 1.44 2.14 --plotting-position Weibull \\
31
+ --information-divergence "Jensen-Shannon"
32
+ """
33
+
34
+ import argparse
35
+
36
+ import numpy as np
37
+
38
+ from .distributions import (
39
+ gev_cdf,
40
+ gev_pdf,
41
+ gev_quantile,
42
+ gev_return_level,
43
+ normal_cdf,
44
+ normal_pdf,
45
+ weibull_cdf,
46
+ weibull_pdf,
47
+ calc_gev_cdf,
48
+ calc_gev_pdf,
49
+ calc_gev_quantile,
50
+ calc_norm_cdf,
51
+ calc_norm_pdf,
52
+ calc_return_level_gev,
53
+ calc_weibull_cdf,
54
+ calc_weibull_pdf,
55
+ )
56
+ from .divergences import (
57
+ beta_divergence,
58
+ jensen_shannon_divergence,
59
+ kl_divergence,
60
+ kl_generalized_divergence,
61
+ kl_symmetric_divergence,
62
+ power_divergence,
63
+ renyi_divergence,
64
+ )
65
+ from .estimators import (
66
+ ML,
67
+ PPID,
68
+ DIVERGENCE_NAMES,
69
+ find_ppid_min,
70
+ )
71
+ from .plotting import (
72
+ PLOTTING_POSITIONS,
73
+ plotting_position_coefficients,
74
+ plotting_positions,
75
+ )
76
+
77
+ __version__ = "0.1.0"
78
+
79
+ __all__ = [
80
+ # distributions
81
+ "normal_cdf", "normal_pdf",
82
+ "gev_cdf", "gev_pdf", "gev_quantile", "gev_return_level",
83
+ "weibull_cdf", "weibull_pdf",
84
+ # plotting positions
85
+ "PLOTTING_POSITIONS", "plotting_position_coefficients",
86
+ "plotting_positions",
87
+ # divergences
88
+ "kl_divergence", "kl_generalized_divergence", "kl_symmetric_divergence",
89
+ "jensen_shannon_divergence", "beta_divergence", "power_divergence",
90
+ "renyi_divergence",
91
+ # estimators
92
+ "ML", "PPID", "find_ppid_min",
93
+ # legacy aliases
94
+ "calc_norm_cdf", "calc_norm_pdf",
95
+ "calc_gev_cdf", "calc_gev_pdf", "calc_gev_quantile",
96
+ "calc_return_level_gev", "calc_weibull_cdf", "calc_weibull_pdf",
97
+ ]
98
+
99
+
100
+ #: Name of the parameter vector entry for each supported distribution.
101
+ _PARAMETER_NAMES = {
102
+ "gev": ("loc", "scale", "shape"),
103
+ "normal": ("loc", "scale"),
104
+ "weibull": ("threshold", "scale", "shape"),
105
+ }
106
+
107
+ #: CDF callable for each supported distribution.
108
+ _DISTRIBUTIONS = {
109
+ "gev": gev_cdf,
110
+ "normal": normal_cdf,
111
+ "weibull": weibull_cdf,
112
+ }
113
+
114
+
115
+ def _initial_parameters(name, xs):
116
+ """Return a sensible parameter guess derived from the sample."""
117
+ loc = float(np.mean(xs))
118
+ scale = float(np.std(xs))
119
+ if name == "normal":
120
+ return [loc, scale]
121
+ if name == "gev":
122
+ return [loc, scale, 0.0]
123
+ # three-parameter Weibull: threshold below the minimum observation,
124
+ # scale on the order of the observed range.
125
+ mn, mx = float(xs.min()), float(xs.max())
126
+ threshold = mn - 0.1 * (mx - mn) - 1e-9
127
+ return [threshold, (mx - mn) + 1e-9, 2.0]
128
+
129
+
130
+ def main(argv=None):
131
+ """Console entry point: fit a distribution to command-line sample data.
132
+
133
+ The parameters are fitted with the :class:`PPID` estimator using the
134
+ plotting position and information divergence given by
135
+ ``--plotting-position`` (default ``Hazen``) and
136
+ ``--information-divergence`` (default ``Kullback-Leibler``).
137
+ """
138
+ parser = argparse.ArgumentParser(
139
+ prog="ppidest",
140
+ description="Fit a univariate distribution by minimizing an "
141
+ "information divergence between plotting-position "
142
+ "empirical gaps and the candidate CDF gaps.")
143
+ parser.add_argument(
144
+ "xs", type=float, nargs="+", metavar="x",
145
+ help="sample values to fit")
146
+ parser.add_argument(
147
+ "-d", "--distribution", choices=sorted(_DISTRIBUTIONS),
148
+ default="gev",
149
+ help="distribution family to fit (default: gev)")
150
+ parser.add_argument(
151
+ "-p", "--plotting-position", choices=sorted(PLOTTING_POSITIONS),
152
+ default="Hazen",
153
+ help="plotting-position scheme (default: Hazen)")
154
+ parser.add_argument(
155
+ "-i", "--information-divergence", choices=sorted(DIVERGENCE_NAMES),
156
+ default="Kullback-Leibler",
157
+ help="information divergence to minimize (default: "
158
+ "Kullback-Leibler)")
159
+ parser.add_argument(
160
+ "--pdiv", type=float, default=0.5, metavar="VALUE",
161
+ help="order/index parameter for the beta, power or Renyi "
162
+ "divergences (default: 0.5)")
163
+ parser.add_argument(
164
+ "--initial", type=float, nargs="+", metavar="PAR",
165
+ help="initial parameters, overriding the data-driven defaults "
166
+ "(e.g. '--initial 0 1 0.25' for GEV)")
167
+ parser.add_argument(
168
+ "--version", action="version", version=f"%(prog)s {__version__}")
169
+ args = parser.parse_args(argv)
170
+
171
+ xs = np.asarray(args.xs)
172
+ if xs.size < 2:
173
+ parser.error("at least two sample values are required")
174
+
175
+ name = args.distribution
176
+ cdf = _DISTRIBUTIONS[name]
177
+ par_names = _PARAMETER_NAMES[name]
178
+ if args.initial is not None:
179
+ if len(args.initial) != len(par_names):
180
+ parser.error(
181
+ f"--initial must provide {len(par_names)} values for "
182
+ f"{name} ({', '.join(par_names)})")
183
+ par_0 = list(args.initial)
184
+ else:
185
+ par_0 = _initial_parameters(name, xs)
186
+
187
+ res = PPID(xs, cdf, plotposition=args.plotting_position,
188
+ divergence=args.information_divergence).find_min_div(
189
+ par_0, pdiv=args.pdiv)
190
+
191
+ header = ("distribution", "plotting position", "information divergence")
192
+ values = (name, args.plotting_position, args.information_divergence)
193
+ for key, value in zip(header, values):
194
+ print(f"{key:26s}: {value}")
195
+ for pname, pvalue in zip(par_names, res.x):
196
+ print(f"{pname:26s}: {pvalue:.6g}")
197
+ print(f"{'minimum divergence':26s}: {res.fun:.6g}")
198
+ print(f"{'converged':26s}: {res.success}")
199
+ return res