diff --git a/pygam/core.py b/pygam/core.py index ce27efbc..2e972e7e 100644 --- a/pygam/core.py +++ b/pygam/core.py @@ -1,6 +1,7 @@ """Core Classes""" import numpy as np +from copy import deepcopy from pygam.utils import flatten, round_to_n_decimal_places @@ -150,15 +151,15 @@ def get_params(self, deep=False): ------- dict """ - attrs = self.__dict__ + attrs =deepcopy(self.__dict__) for attr in self._include: - attrs[attr] = getattr(self, attr) + attrs[attr] = deepcopy(getattr(self, attr)) if deep is True: return attrs return dict( [ - (k, v) + (k, deepcopy(v)) for k, v in list(attrs.items()) if (k[0] != "_") and (k[-1] != "_") and (k not in self._exclude) ] diff --git a/pygam/links.py b/pygam/links.py index cc976eec..c14d27a1 100644 --- a/pygam/links.py +++ b/pygam/links.py @@ -118,8 +118,19 @@ def mu(self, lp, dist): ------- mu : np.array of length n """ - elp = np.exp(lp) - return dist.levels * elp / (elp + 1) + lp = np.asarray(lp) + mu = np.empty_like(lp, dtype=float) + +# positive branch + pos = lp >= 0 + mu[pos] = dist.levels / (1 + np.exp(-lp[pos])) + +# negative branch + neg = ~pos + exp_lp = np.exp(lp[neg]) + mu[neg] = dist.levels * exp_lp / (1 + exp_lp) + + return mu def gradient(self, mu, dist): """ diff --git a/pygam/pygam.py b/pygam/pygam.py index 6f86ae43..f6e0bf19 100644 --- a/pygam/pygam.py +++ b/pygam/pygam.py @@ -8,6 +8,7 @@ import scipy as sp from progressbar import ProgressBar from scipy import stats # noqa: F401 +from sklearn.base import BaseEstimator from pygam.callbacks import ( CALLBACKS, # noqa: F401 @@ -175,7 +176,9 @@ def __init__( self.link = link self.callbacks = callbacks self.verbose = verbose - self.terms = TermList(terms) if isinstance(terms, Term) else terms + self.terms = terms # store original input for get_params() + self._terms = TermList(terms) if isinstance(terms, Term) else terms # internal processed version + self.fit_intercept = fit_intercept for k, v in kwargs.items(): @@ -207,6 +210,14 @@ def __init__( # self.terms.lam = value # else: # self._lam = value + def _get_tags(self): + """Required for sklearn compatibility""" + return { + "requires_y": True, + "non_deterministic": False, + "requires_positive_y": False, + } + @property def _is_fitted(self): @@ -856,6 +867,7 @@ def _on_loop_end(self, variables): for callback in self.callbacks: if hasattr(callback, "on_loop_end"): self.logs_[str(callback)].append(callback.on_loop_end(**variables)) + def fit(self, X, y, weights=None): """Fit the generalized additive model. diff --git a/test.py b/test.py new file mode 100644 index 00000000..f1da53a0 --- /dev/null +++ b/test.py @@ -0,0 +1,3 @@ +from pygam import LinearGAM +gam = LinearGAM() +print(gam._get_tags()) \ No newline at end of file diff --git a/test1.py b/test1.py new file mode 100644 index 00000000..6edce486 --- /dev/null +++ b/test1.py @@ -0,0 +1,14 @@ +from pygam import LinearGAM + +gam = LinearGAM() + +params = gam.get_params() + +print("Before change:", params["callbacks"]) + +# Try modifying returned params +params["callbacks"].append("hack") + +# Check again +params2 = gam.get_params() +print("After change:", params2["callbacks"]) \ No newline at end of file diff --git a/test5.py b/test5.py new file mode 100644 index 00000000..c37173e9 --- /dev/null +++ b/test5.py @@ -0,0 +1,33 @@ +import numpy as np +from pygam import LinearGAM + +X = np.random.rand(100, 1) +y = np.sin(X[:, 0]) + +gam = LinearGAM().fit(X, y) + +print("Model trained successfully\n") + +try: + result = gam.predict([[0.5], [0.2]]) + print("Test 1 Passed: Valid input works") +except Exception as e: + print("Test 1 Failed:", e) + +try: + result = gam.predict([0.5, 0.2]) + print("Test 2 Passed: 1D input handled") +except Exception as e: + print("Test 2 Failed:", e) + +try: + gam.predict([[1, 2], [3, 4]]) + print("Test 3 Failed: Should have raised error") +except ValueError as e: + print("Test 3 Passed:", e) + +try: + gam.predict([[0.5], [np.nan]]) + print("Test 4 Failed: Should have raised error") +except ValueError as e: + print("Test 4 Passed:", e) \ No newline at end of file