Skip to content

Commit 3badcc0

Browse files
committed
add canonical gaussian model
1 parent ed147c4 commit 3badcc0

File tree

3 files changed

+106
-32
lines changed

3 files changed

+106
-32
lines changed

src/regmod/models/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"""
44

55
from .binomial import BinomialModel, CanonicalBinomialModel, create_binomial_model
6-
from .gaussian import GaussianModel
6+
from .gaussian import CanonicalGaussianModel, GaussianModel, create_gaussian_model
77
from .model import Model
88
from .negativebinomial import NegativeBinomialModel
99
from .pogit import PogitModel

src/regmod/models/gaussian.py

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
from scipy.stats import norm
77

88
from regmod._typing import Callable, DataFrame, Matrix, NDArray
9+
from regmod.data import Data
910
from regmod.optimizer import msca_optimize
1011

1112
from .model import Model
12-
from .utils import model_post_init
13+
from .utils import get_params, model_post_init
1314

1415

1516
class GaussianModel(Model):
@@ -31,6 +32,13 @@ def attach_df(self, df: DataFrame):
3132
def hessian_from_gprior(self) -> Matrix:
3233
return self.hmat
3334

35+
def get_lin_param(self, coefs: NDArray) -> NDArray:
36+
mat = self.mat[0]
37+
lin_param = mat.dot(coefs)
38+
if self.params[0].offset is not None:
39+
lin_param += self.data.get_cols(self.params[0].offset)
40+
return lin_param
41+
3442
def objective(self, coefs: NDArray) -> float:
3543
"""Objective function.
3644
Parameters
@@ -158,3 +166,62 @@ def get_ui(self, params: list[NDArray], bounds: tuple[float, float]) -> NDArray:
158166
norm.ppf(bounds[0], loc=mean, scale=sd),
159167
norm.ppf(bounds[1], loc=mean, scale=sd),
160168
]
169+
170+
171+
class CanonicalGaussianModel(GaussianModel):
172+
def __init__(self, data: Data, **kwargs):
173+
super().__init__(data, **kwargs)
174+
if self.params[0].inv_link.name != "identity":
175+
raise ValueError(
176+
"Canonical Gaussian model requires inverse link to be identity."
177+
)
178+
179+
def objective(self, coefs: NDArray) -> float:
180+
weights = self.data.weights * self.data.trim_weights
181+
y = self.get_lin_param(coefs)
182+
183+
prior_obj = self.objective_from_gprior(coefs)
184+
likli_obj = 0.5 * weights.dot((y - self.data.obs) ** 2)
185+
return prior_obj + likli_obj
186+
187+
def gradient(self, coefs: NDArray) -> NDArray:
188+
mat = self.mat[0]
189+
weights = self.data.weights * self.data.trim_weights
190+
y = self.get_lin_param(coefs)
191+
192+
prior_grad = self.gradient_from_gprior(coefs)
193+
likli_grad = mat.T.dot(weights * (y - self.data.obs))
194+
return prior_grad + likli_grad
195+
196+
def hessian(self, coefs: NDArray) -> Matrix:
197+
mat = self.mat[0]
198+
weights = self.data.weights * self.data.trim_weights
199+
likli_hess_scale = weights
200+
201+
prior_hess = self.hessian_from_gprior()
202+
likli_hess_right = mat.scale_rows(likli_hess_scale)
203+
likli_hess = mat.T.dot(likli_hess_right)
204+
205+
return prior_hess + likli_hess
206+
207+
def jacobian2(self, coefs: NDArray) -> NDArray:
208+
mat = self.mat[0]
209+
weights = self.data.weights * self.data.trim_weights
210+
y = self.get_lin_param(coefs)
211+
likli_jac_scale = weights * (y - self.data.obs)
212+
213+
likli_jac = mat.T.scale_cols(likli_jac_scale)
214+
likli_jac2 = likli_jac.dot(likli_jac.T)
215+
return self.hessian_from_gprior() + likli_jac2
216+
217+
218+
def create_gaussian_model(data: Data, **kwargs) -> GaussianModel:
219+
params = get_params(
220+
params=kwargs.get("params"),
221+
param_specs=kwargs.get("param_specs"),
222+
default_param_specs=GaussianModel.default_param_specs,
223+
)
224+
225+
if params[0].inv_link.name == "identity":
226+
return CanonicalGaussianModel(data, params=params)
227+
return GaussianModel(data, params=params)

tests/test_gaussianmodel.py

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
"""
22
Test Gaussian Model
33
"""
4+
45
import numpy as np
56
import pandas as pd
67
import pytest
7-
88
from regmod.data import Data
99
from regmod.function import fun_dict
10-
from regmod.models import GaussianModel
11-
from regmod.prior import (GaussianPrior, SplineGaussianPrior,
12-
SplineUniformPrior, UniformPrior)
10+
from regmod.models import create_gaussian_model
11+
from regmod.prior import (
12+
GaussianPrior,
13+
SplineGaussianPrior,
14+
SplineUniformPrior,
15+
UniformPrior,
16+
)
1317
from regmod.utils import SplineSpecs
1418
from regmod.variable import SplineVariable, Variable
1519

@@ -19,14 +23,14 @@
1923
@pytest.fixture
2024
def data():
2125
num_obs = 5
22-
df = pd.DataFrame({
23-
"obs": np.random.randn(num_obs),
24-
"cov0": np.random.randn(num_obs),
25-
"cov1": np.random.randn(num_obs)
26-
})
27-
return Data(col_obs="obs",
28-
col_covs=["cov0", "cov1"],
29-
df=df)
26+
df = pd.DataFrame(
27+
{
28+
"obs": np.random.randn(num_obs),
29+
"cov0": np.random.randn(num_obs),
30+
"cov1": np.random.randn(num_obs),
31+
}
32+
)
33+
return Data(col_obs="obs", col_covs=["cov0", "cov1"], df=df)
3034

3135

3236
@pytest.fixture
@@ -41,9 +45,9 @@ def uprior():
4145

4246
@pytest.fixture
4347
def spline_specs():
44-
return SplineSpecs(knots=np.linspace(0.0, 1.0, 5),
45-
degree=3,
46-
knots_type="rel_domain")
48+
return SplineSpecs(
49+
knots=np.linspace(0.0, 1.0, 5), degree=3, knots_type="rel_domain"
50+
)
4751

4852

4953
@pytest.fixture
@@ -58,20 +62,21 @@ def spline_uprior():
5862

5963
@pytest.fixture
6064
def var_cov0(gprior, uprior):
61-
return Variable(name="cov0",
62-
priors=[gprior, uprior])
65+
return Variable(name="cov0", priors=[gprior, uprior])
6366

6467

6568
@pytest.fixture
6669
def var_cov1(spline_gprior, spline_uprior, spline_specs):
67-
return SplineVariable(name="cov1",
68-
spline_specs=spline_specs,
69-
priors=[spline_gprior, spline_uprior])
70+
return SplineVariable(
71+
name="cov1", spline_specs=spline_specs, priors=[spline_gprior, spline_uprior]
72+
)
7073

7174

7275
@pytest.fixture
7376
def model(data, var_cov0, var_cov1):
74-
return GaussianModel(data, param_specs={"mu": {"variables": [var_cov0, var_cov1]}})
77+
return create_gaussian_model(
78+
data, param_specs={"mu": {"variables": [var_cov0, var_cov1]}}
79+
)
7580

7681

7782
def test_model_result(model):
@@ -117,7 +122,7 @@ def test_model_gradient(model, inv_link):
117122
tr_grad = np.zeros(model.size)
118123
for i in range(model.size):
119124
coefs_c[i] += 1e-16j
120-
tr_grad[i] = model.objective(coefs_c).imag/1e-16
125+
tr_grad[i] = model.objective(coefs_c).imag / 1e-16
121126
coefs_c[i] -= 1e-16j
122127
assert np.allclose(my_grad, tr_grad)
123128

@@ -132,7 +137,7 @@ def test_model_hessian(model, inv_link):
132137
for i in range(model.size):
133138
for j in range(model.size):
134139
coefs_c[j] += 1e-16j
135-
tr_hess[i][j] = model.gradient(coefs_c).imag[i]/1e-16
140+
tr_hess[i][j] = model.gradient(coefs_c).imag[i] / 1e-16
136141
coefs_c[j] -= 1e-16j
137142

138143
assert np.allclose(my_hess, tr_hess)
@@ -152,25 +157,27 @@ def test_model_jacobian2(model):
152157

153158
mat = model.mat[0].to_numpy()
154159
param = model.get_params(beta)[0]
155-
residual = (model.data.obs - param)*np.sqrt(model.data.weights)
156-
jacobian = mat.T*residual
160+
residual = (model.data.obs - param) * np.sqrt(model.data.weights)
161+
jacobian = mat.T * residual
157162
true_jacobian2 = jacobian.dot(jacobian.T) + model.hessian_from_gprior()
158163

159164
assert np.allclose(jacobian2, true_jacobian2)
160165

161166

162167
def test_model_no_variables():
163168
num_obs = 5
164-
df = pd.DataFrame({
165-
"obs": np.random.randn(num_obs),
166-
"offset": np.ones(num_obs),
167-
})
169+
df = pd.DataFrame(
170+
{
171+
"obs": np.random.randn(num_obs),
172+
"offset": np.ones(num_obs),
173+
}
174+
)
168175
data = Data(
169176
col_obs="obs",
170177
col_offset="offset",
171178
df=df,
172179
)
173-
model = GaussianModel(data, param_specs={"mu": {"offset": "offset"}})
180+
model = create_gaussian_model(data, param_specs={"mu": {"offset": "offset"}})
174181
coefs = np.array([])
175182
grad = model.gradient(coefs)
176183
hessian = model.hessian(coefs)

0 commit comments

Comments
 (0)