Skip to content

Added Weibull Distribution #386

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

Merged
merged 2 commits into from
May 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ngboost/distns/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .normal import Normal, NormalFixedMean, NormalFixedVar
from .poisson import Poisson
from .t import T, TFixedDf, TFixedDfFixedVar
from .weibull import Weibull

__all__ = [
"Bernoulli",
Expand All @@ -30,4 +31,5 @@
"T",
"TFixedDf",
"TFixedDfFixedVar",
"Weibull",
]
65 changes: 65 additions & 0 deletions ngboost/distns/weibull.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""The NGBoost Weibull distribution and scores"""
import numpy as np
from scipy.stats import weibull_min as dist

from ngboost.distns.distn import RegressionDistn
from ngboost.scores import LogScore


class WeibullLogScore(LogScore):
def score(self, Y):
return -self.dist.logpdf(Y)

def d_score(self, Y):
D = np.zeros((len(Y), 2))
shared_term = self.shape * ((Y / self.scale) ** self.shape - 1)
D[:, 0] = shared_term * np.log(Y / self.scale) - 1
D[:, 1] = -shared_term

return D

def metric(self):
gamma = 0.5772156649 # Euler's constant
FI = np.zeros((self.scale.shape[0], 2, 2))
FI[:, 0, 0] = (np.pi**2 / 6) + (1 - gamma) ** 2
FI[:, 1, 0] = -self.shape * (1 - gamma)
FI[:, 0, 1] = FI[:, 1, 0]
FI[:, 1, 1] = self.shape**2

return FI


class Weibull(RegressionDistn):
"""
Implements the Weibull distribution for NGBoost.
The Weibull distribution has two parameters, shape and scale.
The scipy loc parameter is held constant for this implementation.
LogScore is supported for the Weibull distribution.
"""

n_params = 2
scores = [WeibullLogScore]

# pylint: disable=super-init-not-called
def __init__(self, params):
self._params = params
self.shape = np.exp(params[0]) # shape (c)
self.scale = np.exp(params[1]) # scale (labmda)
self.dist = dist(c=self.shape, loc=0, scale=self.scale)

def fit(Y):
shape, _loc, scale = dist.fit(Y, floc=0) # hold loc constant
return np.array([np.log(shape), np.log(scale)])

def sample(self, m):
return np.array([self.dist.rvs() for i in range(m)])

def __getattr__(self, name):
if name in dir(self.dist):
return getattr(self.dist, name)
return None

@property
def params(self):
return {"shape": self.shape, "scale": self.scale}
2 changes: 2 additions & 0 deletions tests/test_distns.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
T,
TFixedDf,
TFixedDfFixedVar,
Weibull,
k_categorical,
)
from ngboost.scores import CRPScore, LogScore, Score
Expand Down Expand Up @@ -74,6 +75,7 @@ def is_t_distribution(
TFixedDf,
TFixedDfFixedVar,
Cauchy,
Weibull,
],
)
@pytest.mark.parametrize(
Expand Down
2 changes: 2 additions & 0 deletions tests/test_score.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
T,
TFixedDf,
TFixedDfFixedVar,
Weibull,
)
from ngboost.manifold import manifold
from ngboost.scores import CRPScore, LogScore, Score
Expand Down Expand Up @@ -100,6 +101,7 @@ def idfn(dist_score: DistScore):
(Laplace, LogScore),
(Poisson, LogScore),
(Gamma, LogScore),
(Weibull, LogScore),
] + [(MultivariateNormal(i), LogScore) for i in range(2, 5)]
# Fill in the dist, score pair to test the gradient
# Tests all in TEST_METRIC by default
Expand Down