Skip to content

Commit 22f9ef7

Browse files
authored
feat: added halfnormal distribution (#387)
1 parent 5faed11 commit 22f9ef7

File tree

4 files changed

+61
-0
lines changed

4 files changed

+61
-0
lines changed

ngboost/distns/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from .distn import ClassificationDistn, Distn, RegressionDistn
55
from .exponential import Exponential
66
from .gamma import Gamma
7+
from .halfnormal import HalfNormal
78
from .laplace import Laplace
89
from .lognormal import LogNormal
910
from .multivariate_normal import MultivariateNormal
@@ -21,6 +22,7 @@
2122
"RegressionDistn",
2223
"Exponential",
2324
"Gamma",
25+
"HalfNormal",
2426
"Laplace",
2527
"LogNormal",
2628
"MultivariateNormal",

ngboost/distns/halfnormal.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""The NGBoost Half-Normal distribution and scores"""
2+
import numpy as np
3+
from scipy.stats import halfnorm as dist
4+
5+
from ngboost.distns.distn import RegressionDistn
6+
from ngboost.scores import LogScore
7+
8+
9+
class HalfNormalLogScore(LogScore):
10+
def score(self, Y):
11+
return -self.dist.logpdf(Y)
12+
13+
def d_score(self, Y):
14+
D = np.zeros((len(Y), 1))
15+
D[:, 0] = (self.scale**2 - Y**2) / self.scale**2 # dL/d(log(scale))
16+
return D
17+
18+
def metric(self):
19+
FI = 2 * np.ones_like(self.scale)
20+
return FI[:, np.newaxis, np.newaxis]
21+
22+
23+
class HalfNormal(RegressionDistn):
24+
"""
25+
Implements the Half-Normal distribution for NGBoost.
26+
27+
The Half-Normal distribution has one parameter, scale.
28+
The scipy loc parameter is held constant at zero for this implementation.
29+
LogScore is supported for the Half-Normal distribution.
30+
"""
31+
32+
n_params = 1
33+
scores = [HalfNormalLogScore]
34+
35+
# pylint: disable=super-init-not-called
36+
def __init__(self, params):
37+
self._params = params
38+
self.scale = np.exp(params[0]) # scale (sigma)
39+
self.dist = dist(loc=0, scale=self.scale)
40+
41+
def fit(Y):
42+
_loc, scale = dist.fit(Y, floc=0) # loc held constant
43+
return np.array([np.log(scale)])
44+
45+
def sample(self, m):
46+
return np.array([self.dist.rvs() for i in range(m)])
47+
48+
def __getattr__(self, name):
49+
if name in dir(self.dist):
50+
return getattr(self.dist, name)
51+
return None
52+
53+
@property
54+
def params(self):
55+
return {"loc": np.zeros(shape=self.scale.shape), "scale": self.scale}

tests/test_distns.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
Distn,
1414
Exponential,
1515
Gamma,
16+
HalfNormal,
1617
LogNormal,
1718
MultivariateNormal,
1819
Normal,
@@ -68,6 +69,7 @@ def is_t_distribution(
6869
Normal,
6970
NormalFixedVar,
7071
NormalFixedMean,
72+
HalfNormal,
7173
LogNormal,
7274
Exponential,
7375
Gamma,

tests/test_score.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
Cauchy,
1010
Distn,
1111
Gamma,
12+
HalfNormal,
1213
Laplace,
1314
MultivariateNormal,
1415
Normal,
@@ -97,6 +98,7 @@ def idfn(dist_score: DistScore):
9798
TEST_METRIC: List[DistScore] = [
9899
(Normal, LogScore),
99100
(Normal, CRPScore),
101+
(HalfNormal, LogScore),
100102
(TFixedDfFixedVar, LogScore),
101103
(Laplace, LogScore),
102104
(Poisson, LogScore),

0 commit comments

Comments
 (0)