Skip to content
Open
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Binary file modified .gitignore
Binary file not shown.
13 changes: 3 additions & 10 deletions docs/notebooks/quick_start.ipynb

Large diffs are not rendered by default.

55 changes: 55 additions & 0 deletions examples/gam_regression.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""
Basic Regression Example using pyGAM

This example demonstrates:
1. Generating synthetic nonlinear data
2. Training a GAM regression model
3. Making predictions with confidence intervals
4. Visualizing the results
"""

import numpy as np
import matplotlib.pyplot as plt
from pygam import LinearGAM, s


np.random.seed(0)

X = np.linspace(0, 5, 100)
y = np.sin(X) + np.random.normal(scale=0.2, size=100)


X = X.reshape(-1, 1)


gam = LinearGAM(s(0)).fit(X, y)


X_pred = np.linspace(0, 5, 200).reshape(-1, 1)
y_pred = gam.predict(X_pred)
y_conf = gam.confidence_intervals(X_pred)


#Visualize
plt.figure(figsize=(8, 5))
plt.scatter(X, y, label="Data", alpha=0.6)
# GAM prediction line
plt.plot(X_pred, y_pred, color="red", label="GAM Fit")

# Confidence interval shading
plt.fill_between(
X_pred.flatten(),
y_conf[:, 0],
y_conf[:, 1],
color="red",
alpha=0.2,
label="Confidence Interval"
)

# Labels and title
plt.title("pyGAM Regression Example with Confidence Intervals")
plt.xlabel("X")
plt.ylabel("y")
plt.legend()
plt.tight_layout()
plt.show()
112 changes: 77 additions & 35 deletions pygam/pygam.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import numpy as np
import scipy as sp
from joblib import Parallel, delayed
from progressbar import ProgressBar
from scipy import stats # noqa: F401

Expand Down Expand Up @@ -1813,6 +1814,7 @@ def gridsearch(
return_scores=False,
keep_best=True,
objective="auto",
n_jobs=1,
progress=True,
**param_grids,
):
Expand Down Expand Up @@ -1857,8 +1859,15 @@ def gridsearch(
If `auto`, then grid search will optimize `GCV` for models with unknown
scale and `UBRE` for models with known scale.

n_jobs : int, optional
Number of jobs for parallel computation.
``1`` means sequential (default). ``-1`` means using all
available cores. Values greater than ``1`` specify the
exact number of cores to use. When ``n_jobs != 1``,
warm-starting from previously fitted models is disabled.

progress : bool, optional
whether to display a progress bar
whether to display a progress bar. Ignored when ``n_jobs != 1``.

**kwargs
pairs of parameters and iterables of floats, or
Expand Down Expand Up @@ -2030,44 +2039,75 @@ def gridsearch(
best_model = models[-1]
best_score = scores[-1]

# make progressbar optional
if progress:
pbar = ProgressBar()
else:
if n_jobs == 1:
# === sequential path (preserves warm-start) ===

def pbar(x):
return x
# make progressbar optional
if progress:
pbar = ProgressBar()
else:

# loop through candidate model params
for param_grid in pbar(param_grid_list):
try:
# try fitting
# define new model
gam = deepcopy(self)
gam.set_params(self.get_params())
gam.set_params(**param_grid)

# warm start with parameters from previous build
if models:
coef = models[-1].coef_
gam.set_params(coef_=coef, force=True, verbose=False)
gam.fit(X, y, weights)

except ValueError as error:
msg = str(error) + "\non model with params:\n" + str(param_grid)
msg += "\nskipping...\n"
if self.verbose:
warnings.warn(msg)
continue
def pbar(x):
return x

# loop through candidate model params
for param_grid in pbar(param_grid_list):
try:
# try fitting
# define new model
gam = deepcopy(self)
gam.set_params(self.get_params())
gam.set_params(**param_grid)

# warm start with parameters from previous build
if models:
coef = models[-1].coef_
gam.set_params(coef_=coef, force=True, verbose=False)
gam.fit(X, y, weights)

except ValueError as error:
msg = str(error) + "\non model with params:\n" + str(param_grid)
msg += "\nskipping...\n"
if self.verbose:
warnings.warn(msg)
continue

# record results
models.append(gam)
scores.append(gam.statistics_[objective])

# track best
if scores[-1] < best_score:
best_model = models[-1]
best_score = scores[-1]

# record results
models.append(gam)
scores.append(gam.statistics_[objective])
else:
# === parallel path ===
def _fit_one(base_gam, params, obj, X, y, weights):
try:
gam = deepcopy(base_gam)
gam.set_params(**base_gam.get_params())
gam.set_params(**params)
gam.fit(X, y, weights)
return gam, gam.statistics_[obj]
except ValueError as error:
if base_gam.verbose:
warnings.warn(str(error))
return None

results = Parallel(n_jobs=n_jobs)(
delayed(_fit_one)(self, pg, objective, X, y, weights)
for pg in param_grid_list
)

# track best
if scores[-1] < best_score:
best_model = models[-1]
best_score = scores[-1]
for result in results:
if result is not None:
gam, score = result
models.append(gam)
scores.append(score)
if score < best_score:
best_model = gam
best_score = score

# problems
if len(models) == 0:
Expand Down Expand Up @@ -2967,6 +3007,7 @@ def gridsearch(
return_scores=False,
keep_best=True,
objective="auto",
n_jobs=1,
**param_grids,
):
"""
Expand Down Expand Up @@ -3042,6 +3083,7 @@ def gridsearch(
return_scores=return_scores,
keep_best=keep_best,
objective=objective,
n_jobs=n_jobs,
**param_grids,
)

Expand Down
29 changes: 29 additions & 0 deletions pygam/tests/test_gridsearch.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,3 +266,32 @@ def test_gridsearch_works_on_Series_REGRESSION():
# Series
gam = LinearGAM().gridsearch(X[0], y)
assert gam._is_fitted


def test_gridsearch_parallel_matches_sequential(mcycle_X_y):
n = 5
X, y = mcycle_X_y
lam = np.logspace(-3, 3, n)

gam_seq = LinearGAM().gridsearch(X, y, lam=lam, n_jobs=1, progress=False)
gam_par = LinearGAM().gridsearch(X, y, lam=lam, n_jobs=2, progress=False)

assert np.isclose(gam_seq.statistics_["GCV"], gam_par.statistics_["GCV"])


def test_gridsearch_parallel_n_jobs_minus_one(mcycle_X_y):
X, y = mcycle_X_y

gam = LinearGAM().gridsearch(X, y, lam=np.logspace(-3, 3, 5), n_jobs=-1)
assert gam._is_fitted


def test_gridsearch_parallel_return_scores(mcycle_X_y):
n = 5
X, y = mcycle_X_y

scores = LinearGAM().gridsearch(
X, y, lam=np.logspace(-3, 3, n), n_jobs=2, return_scores=True
)

assert len(scores) == n
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ classifiers = [
]

dependencies = [
"joblib>=1.3.0",
"numpy>=1.5.0",
"progressbar2>=4.2.0,<5",
"scipy>=1.11.1,<1.17",
Expand Down
Loading
Loading