-
Notifications
You must be signed in to change notification settings - Fork 197
Add DistfitFitter distribution fitter wrapping the distfit package #1097
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
pyarchana
wants to merge
7
commits into
sktime:main
Choose a base branch
from
pyarchana:1086-distfit-fitter
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 5 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
89582c0
Add DistfitFitter distribution fitter wrapping the distfit package
pyarchana dcb2966
Add areychana to contributors list
pyarchana 87fac97
update username to pyarchana in contributors list
pyarchana b47ea7c
Merge branch 'upstream-main' into 1086-distfit-fitter
pyarchana e0b5df0
Document fitted attributes and add DistfitFitter to API reference
pyarchana 444b52e
use run_test_for_class in distfitter tests
pyarchana 14eb396
Merge branch 'main' of https://github.com/sktime/skpro into 1086-dist…
pyarchana File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,12 @@ | ||
| """Distribution fitter estimators.""" | ||
| # copyright: skpro developers, BSD-3-Clause License (see LICENSE file) | ||
|
|
||
| from skpro.distfitter._distfitfitter import DistfitFitter | ||
| from skpro.distfitter._momfitter import MOMFitter | ||
| from skpro.distfitter._normalfitter import NormalFitter | ||
|
|
||
| __all__ = [ | ||
| "DistfitFitter", | ||
| "NormalFitter", | ||
| "MOMFitter", | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| """Generic scalar distribution wrapping a named scipy distribution. | ||
|
|
||
| Used to turn the output of ``distfit`` (a scipy distribution name plus | ||
| shape/loc/scale parameters) into a fitted ``skpro`` ``BaseDistribution``, | ||
| by reusing the existing ``_ScipyAdapter`` machinery. | ||
| """ | ||
| # copyright: skpro developers, BSD-3-Clause License (see LICENSE file) | ||
|
|
||
| from skpro.distributions.adapters.scipy import _ScipyAdapter | ||
|
|
||
| __all__ = ["_DistfitDistribution"] | ||
|
|
||
|
|
||
| class _DistfitDistribution(_ScipyAdapter): | ||
| """Scalar distribution wrapping a scipy distribution selected by ``distfit``. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| dist_name : str | ||
| Name of a distribution in ``scipy.stats``, e.g. ``"norm"``, ``"gamma"``. | ||
| shape_args : tuple, optional (default=()) | ||
| Positional shape parameters for the ``scipy.stats`` distribution. | ||
| dist_loc : float, optional (default=0.0) | ||
| Location parameter, passed as ``loc`` to the ``scipy.stats`` distribution. | ||
| dist_scale : float, optional (default=1.0) | ||
| Scale parameter, passed as ``scale`` to the ``scipy.stats`` distribution. | ||
| """ | ||
|
|
||
| _tags = { | ||
| "authors": ["areychana"], | ||
| "capabilities:exact": ["mean", "var", "pdf", "log_pdf", "cdf", "ppf"], | ||
| "distr:measuretype": "continuous", | ||
| "distr:paramtype": "parametric", | ||
| } | ||
|
|
||
| def __init__(self, dist_name, shape_args=(), dist_loc=0.0, dist_scale=1.0): | ||
| self.dist_name = dist_name | ||
| self.shape_args = shape_args | ||
| self.dist_loc = dist_loc | ||
| self.dist_scale = dist_scale | ||
|
|
||
| super().__init__(index=None, columns=None) | ||
|
|
||
| def _get_scipy_object(self): | ||
| import scipy.stats | ||
|
|
||
| return getattr(scipy.stats, self.dist_name) | ||
|
|
||
| def _get_scipy_param(self): | ||
| args = list(self.shape_args) | ||
| kwds = {"loc": self.dist_loc, "scale": self.dist_scale} | ||
| return args, kwds |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| """Distribution fitter wrapping the distfit package.""" | ||
| # copyright: skpro developers, BSD-3-Clause License (see LICENSE file) | ||
|
|
||
| from skpro.distfitter.base import BaseDistFitter | ||
|
|
||
| __author__ = ["areychana"] | ||
|
|
||
|
|
||
| class DistfitFitter(BaseDistFitter): | ||
| """Fit a parametric distribution to data using the ``distfit`` package. | ||
|
|
||
| Wraps `distfit <https://github.com/erdogant/distfit>`_'s parametric fitting | ||
| procedure: a set of candidate ``scipy.stats`` distributions is fitted to the | ||
| data, scored by a goodness-of-fit statistic, and the best-scoring | ||
| distribution is returned as a fitted ``skpro`` scalar distribution. | ||
|
|
||
| Only ``distfit``'s ``method="parametric"`` mode is currently supported. | ||
| ``distfit``'s ``"quantile"``, ``"percentile"``, and ``"discrete"`` modes | ||
| produce differently shaped model output that this fitter does not (yet) | ||
| convert into a ``skpro`` distribution. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| distr : str or list of str, optional (default="popular") | ||
| Candidate distribution(s) to fit and compare, passed to ``distfit``. | ||
| ``"popular"`` tests ``[norm, expon, pareto, dweibull, t, genextreme, | ||
| gamma, lognorm, beta, uniform, loggamma]``; ``"full"`` tests all | ||
| ``scipy.stats`` continuous distributions; a single name (e.g. | ||
| ``"norm"``) or a list of names restricts to those distributions. | ||
| stats : str, optional (default="RSS") | ||
| Goodness-of-fit statistic used by ``distfit`` to rank candidates. | ||
| One of ``"RSS"``, ``"wasserstein"``, ``"ks"``, ``"energy"``, | ||
| ``"goodness_of_fit"``. | ||
| bins : int or "auto", optional (default="auto") | ||
| Histogram bin size used internally by ``distfit``. | ||
| random_state : int, optional (default=None) | ||
| Random state passed to ``distfit``. | ||
|
|
||
| Attributes | ||
| ---------- | ||
| dist_name_ : str | ||
| Name of the best-fitting ``scipy.stats`` distribution, as chosen by | ||
| ``distfit``. | ||
| shape_args_ : tuple | ||
| Fitted shape parameters for ``dist_name_``, as returned by ``distfit``. | ||
| dist_loc_ : float | ||
| Fitted location parameter for ``dist_name_``. | ||
| dist_scale_ : float | ||
| Fitted scale parameter for ``dist_name_``. | ||
| fit_summary_ : pandas.DataFrame | ||
| Full ``distfit`` summary table of all candidate distributions tried, | ||
| with their scores. | ||
|
|
||
| Examples | ||
| -------- | ||
| >>> import pandas as pd | ||
| >>> from skpro.distfitter import DistfitFitter | ||
| >>> X = pd.DataFrame([1.0, 2.0, 3.0, 4.0, 5.0, 4.5, 3.5, 2.5, 1.5]) | ||
|
|
||
| >>> fitter = DistfitFitter(distr="norm") | ||
| >>> fitter.fit(X) | ||
| DistfitFitter(distr='norm') | ||
| >>> dist = fitter.proba() | ||
| """ | ||
|
|
||
| _tags = { | ||
| "authors": ["areychana"], | ||
| "python_dependencies": ["distfit"], | ||
| } | ||
|
|
||
| def __init__(self, distr="popular", stats="RSS", bins="auto", random_state=None): | ||
| self.distr = distr | ||
| self.stats = stats | ||
| self.bins = bins | ||
| self.random_state = random_state | ||
|
|
||
| super().__init__() | ||
|
|
||
| def _fit(self, X, C=None): | ||
| """Fit the best-scoring distfit distribution to the data. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| X : pandas DataFrame | ||
| Data to fit the distribution to. | ||
| C : ignored | ||
|
|
||
| Returns | ||
| ------- | ||
| self : reference to self | ||
| """ | ||
| from distfit import distfit as _distfit | ||
|
|
||
| vals = X.values.ravel() | ||
|
|
||
| dfit = _distfit( | ||
| method="parametric", | ||
| distr=self.distr, | ||
| stats=self.stats, | ||
| bins=self.bins, | ||
| random_state=self.random_state, | ||
| verbose="warning", | ||
| ) | ||
| dfit.fit_transform(vals) | ||
|
|
||
| model = dfit.model | ||
| self.dist_name_ = model["name"] | ||
| self.shape_args_ = tuple(model["arg"]) | ||
| self.dist_loc_ = float(model["loc"]) | ||
| self.dist_scale_ = float(model["scale"]) | ||
| self.fit_summary_ = dfit.summary | ||
|
|
||
| return self | ||
|
|
||
| def _proba(self): | ||
| """Return the best-fitting distribution found by distfit. | ||
|
|
||
| Returns | ||
| ------- | ||
| dist : skpro BaseDistribution (scalar) | ||
| """ | ||
| from skpro.distfitter._distfit_adapter import _DistfitDistribution | ||
|
|
||
| return _DistfitDistribution( | ||
| dist_name=self.dist_name_, | ||
| shape_args=self.shape_args_, | ||
| dist_loc=self.dist_loc_, | ||
| dist_scale=self.dist_scale_, | ||
| ) | ||
|
|
||
| @classmethod | ||
| def get_test_params(cls, parameter_set="default"): | ||
| """Return testing parameter settings for the estimator. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| parameter_set : str, default="default" | ||
| Name of the set of test parameters to return. | ||
|
|
||
| Returns | ||
| ------- | ||
| params : dict or list of dict | ||
| Parameters to create testing instances of the class. | ||
| """ | ||
| params1 = {"distr": "norm"} | ||
| params2 = {"distr": ["norm", "expon"], "stats": "wasserstein"} | ||
| return [params1, params2] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| """Tests for the DistfitFitter distribution fitter.""" | ||
| # copyright: skpro developers, BSD-3-Clause License (see LICENSE file) | ||
|
|
||
| import numpy as np | ||
| import pandas as pd | ||
| import pytest | ||
| from skbase.utils.dependencies import _check_soft_dependencies | ||
|
|
||
| from skpro.distributions.base import BaseDistribution | ||
|
|
||
| DISTFIT_AVAILABLE = _check_soft_dependencies("distfit", severity="none") | ||
|
|
||
|
|
||
| @pytest.mark.skipif( | ||
| not DISTFIT_AVAILABLE, reason="skip test if required soft dependency not present" | ||
| ) | ||
| def test_distfitfitter_fits_known_normal(): | ||
| """DistfitFitter restricted to 'norm' recovers close to true mean/scale.""" | ||
| from skpro.distfitter import DistfitFitter | ||
|
|
||
| rng = np.random.RandomState(42) | ||
| X = pd.DataFrame(rng.normal(loc=5.0, scale=2.0, size=1000)) | ||
|
|
||
| fitter = DistfitFitter(distr="norm") | ||
| fitter.fit(X) | ||
|
|
||
| assert fitter.dist_name_ == "norm" | ||
|
|
||
| dist = fitter.proba() | ||
|
|
||
| assert isinstance(dist, BaseDistribution) | ||
| assert dist.ndim == 0 | ||
|
|
||
| mean = float(dist.mean()) | ||
| var = float(dist.var()) | ||
|
|
||
| assert np.isclose(mean, 5.0, atol=0.3) | ||
| assert np.isclose(var, 4.0, atol=1.0) | ||
|
|
||
|
|
||
| @pytest.mark.skipif( | ||
| not DISTFIT_AVAILABLE, reason="skip test if required soft dependency not present" | ||
| ) | ||
| def test_distfitfitter_stores_fit_summary(): | ||
| """DistfitFitter stores a summary table of all candidate distributions.""" | ||
| from skpro.distfitter import DistfitFitter | ||
|
|
||
| rng = np.random.RandomState(0) | ||
| X = pd.DataFrame(rng.normal(size=200)) | ||
|
|
||
| fitter = DistfitFitter(distr=["norm", "expon"]) | ||
| fitter.fit(X) | ||
|
|
||
| assert fitter.fit_summary_ is not None | ||
| assert len(fitter.fit_summary_) == 2 | ||
| assert set(fitter.fit_summary_["name"]) == {"norm", "expon"} | ||
|
|
||
|
|
||
| @pytest.mark.skipif( | ||
| not DISTFIT_AVAILABLE, reason="skip test if required soft dependency not present" | ||
| ) | ||
| def test_distfitfitter_proba_before_fit_raises(): | ||
| """Calling proba() before fit() must raise, per BaseDistFitter contract.""" | ||
| from skpro.distfitter import DistfitFitter | ||
|
|
||
| fitter = DistfitFitter(distr="norm") | ||
|
|
||
| with pytest.raises(ValueError): | ||
| fitter.proba() | ||
|
|
||
|
|
||
| @pytest.mark.skipif( | ||
| not DISTFIT_AVAILABLE, reason="skip test if required soft dependency not present" | ||
| ) | ||
| def test_distfitfitter_get_params_roundtrip(): | ||
| """get_params/set_params/clone round-trip as expected for skpro estimators.""" | ||
| from skpro.distfitter import DistfitFitter | ||
|
|
||
| fitter = DistfitFitter(distr="norm", stats="wasserstein") | ||
| params = fitter.get_params() | ||
|
|
||
| assert params["distr"] == "norm" | ||
| assert params["stats"] == "wasserstein" | ||
|
|
||
| clone = fitter.clone() | ||
| assert clone.get_params() == fitter.get_params() |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this are the not documentted, Should't we document this?