diff --git a/.gitignore b/.gitignore
index 902cf039..78671e1e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,6 @@
# Compiled source #
###################
*.pyc
-*.ipynb
# Packages #
############
@@ -36,3 +35,11 @@ Thumbs.db
dist
.cache
+# Documentation
+###############
+_build/
+.ipynb_checkpoints/
+
+# PyCharm
+#########
+.idea/
diff --git a/README.md b/README.md
index 9b64d662..bd677738 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ Generalized Additive Models in Python.
-## Tutorial
+## Documentation
[pyGAM: Getting started with Generalized Additive Models in Python](https://medium.com/@jpoberhauser/pygam-getting-started-with-generalized-additive-models-in-python-457df5b4705f)
## Installation
@@ -72,359 +72,6 @@ GAMs extend generalized linear models by allowing non-linear functions of featur
The result is a very flexible model, where it is easy to incorporate prior knowledge and control overfitting.
-
-## Regression
-For **regression** problems, we can use a **linear GAM** which models:
-
-![alt tag](http://latex.codecogs.com/svg.latex?\mathbb{E}[y|X]=\beta_0+f_1(X_1)+f_2(X_2)+\dots+f_p(X_p))
-
-```python
-from pygam import LinearGAM, s, f
-from pygam.datasets import wage
-
-X, y = wage(return_X_y=True)
-
-gam = LinearGAM(s(0) + s(1) + f(2)).gridsearch(X, y)
-
-fig, axs = plt.subplots(1, 3)
-titles = ['year', 'age', 'education']
-
-for i, ax in enumerate(axs):
- XX = gam.generate_X_grid(term=i)
- pdep, confi = gam.partial_dependence(term=i, width=.95)
-
- ax.plot(XX[:, i], pdep)
- ax.plot(XX[:, i], confi, c='r', ls='--')
- ax.set_title(titles[i])
-```
-
-
-Even though we allowed **n_splines=20** per numerical feature, our **smoothing penalty** reduces us to just 19 **effective degrees of freedom**:
-
-```
-gam.summary()
-
-LinearGAM
-=============================================== ==========================================================
-Distribution: NormalDist Effective DoF: 19.2602
-Link Function: IdentityLink Log Likelihood: -24116.7451
-Number of Samples: 3000 AIC: 48274.0107
- AICc: 48274.2999
- GCV: 1250.3656
- Scale: 1235.9245
- Pseudo R-Squared: 0.2945
-==========================================================================================================
-Feature Function Lambda Rank EDoF P > x Sig. Code
-================================= ==================== ============ ============ ============ ============
-s(0) [15.8489] 20 6.9 5.52e-03 **
-s(1) [15.8489] 20 8.5 1.11e-16 ***
-f(2) [15.8489] 5 3.8 1.11e-16 ***
-intercept 0 1 0.0 1.11e-16 ***
-==========================================================================================================
-Significance codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
-```
-
-
-With **LinearGAMs**, we can also check the **prediction intervals**:
-
-```python
-from pygam import LinearGAM
-from pygam.datasets import mcycle
-
-X, y = mcycle(return_X_y=True)
-
-gam = LinearGAM().gridsearch(X, y)
-XX = gam.generate_X_grid(term=0)
-
-plt.plot(XX, gam.predict(XX), 'r--')
-plt.plot(XX, gam.prediction_intervals(XX, width=.95), color='b', ls='--')
-
-plt.scatter(X, y, facecolor='gray', edgecolors='none')
-plt.title('95% prediction interval')
-```
-
-
-And simulate from the posterior:
-
-```python
-# continuing last example with the mcycle dataset
-for response in gam.sample(X, y, quantity='y', n_draws=50, sample_at_X=XX):
- plt.scatter(XX, response, alpha=.03, color='k')
-plt.plot(XX, gam.predict(XX), 'r--')
-plt.plot(XX, gam.prediction_intervals(XX, width=.95), color='b', ls='--')
-plt.title('draw samples from the posterior of the coefficients')
-```
-
-
-
-## Classification
-For **binary classification** problems, we can use a **logistic GAM** which models:
-
-![alt tag](http://latex.codecogs.com/svg.latex?log\left(\frac{P(y=1|X)}{P(y=0|X)}\right)=\beta_0+f_1(X_1)+f_2(X_2)+\dots+f_p(X_p))
-
-```python
-from pygam import LogisticGAM, s, f
-from pygam.datasets import default
-
-X, y = default(return_X_y=True)
-
-gam = LogisticGAM(f(0) + s(1) + s(2)).gridsearch(X, y)
-
-fig, axs = plt.subplots(1, 3)
-titles = ['student', 'balance', 'income']
-
-for i, ax in enumerate(axs):
- XX = gam.generate_X_grid(term=i)
- pdep, confi = gam.partial_dependence(term=i, width=.95)
-
- ax.plot(XX[:, i], pdep)
- ax.plot(XX[:, i], confi, c='r', ls='--')
- ax.set_title(titles[i])
-
-# and check the accuracy
-gam.accuracy(X, y)
-```
-
-
-Since the **scale** of the **Binomial distribution** is known, our gridsearch minimizes the **Un-Biased Risk Estimator** (UBRE) objective:
-
-```
-gam.summary()
-
-LogisticGAM
-=============================================== ==========================================================
-Distribution: BinomialDist Effective DoF: 3.8047
-Link Function: LogitLink Log Likelihood: -788.877
-Number of Samples: 10000 AIC: 1585.3634
- AICc: 1585.369
- UBRE: 2.1588
- Scale: 1.0
- Pseudo R-Squared: 0.4598
-==========================================================================================================
-Feature Function Lambda Rank EDoF P > x Sig. Code
-================================= ==================== ============ ============ ============ ============
-f(0) [1000.] 2 1.7 4.61e-03 **
-s(1) [1000.] 20 1.2 0.00e+00 ***
-s(2) [1000.] 20 0.8 3.29e-02 *
-intercept 0 1 0.0 0.00e+00 ***
-==========================================================================================================
-Significance codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
-```
-
-
-## Poisson and Histogram Smoothing
-We can intuitively perform **histogram smoothing** by modeling the counts in each bin
-as being distributed Poisson via **PoissonGAM**.
-
-```python
-from pygam import PoissonGAM
-from pygam.datasets import faithful
-
-X, y = faithful(return_X_y=True)
-
-gam = PoissonGAM().gridsearch(X, y)
-
-plt.hist(faithful(return_X_y=False)['eruptions'], bins=200, color='k');
-plt.plot(X, gam.predict(X), color='r')
-plt.title('Best Lambda: {0:.2f}'.format(gam.lam[0][0]));
-```
-
-
-## Terms and Interactions
-
-pyGAM can also fit interactions using tensor products via `te()`
-```python
-from pygam import LinearGAM, s, te
-from pygam.datasets import chicago
-
-X, y = chicago(return_X_y=True)
-
-gam = PoissonGAM(s(0, n_splines=200) + te(3, 1) + s(2)).fit(X, y)
-```
-
-and plot a 3D surface:
-
-```python
-XX = gam.generate_X_grid(term=0, meshgrid=True)
-Z = gam.partial_dependence(term=0, X=XX, meshgrid=True)
-
-from mpl_toolkits import mplot3d
-ax = plt.axes(projection='3d')
-ax.plot_surface(XX[0], XX[1], Z, cmap='viridis')
-```
-
-
-
-For simple interactions it is sometimes useful to add a by-variable to a term
-
-```python
-from pygam import LinearGAM, s
-from pygam.datasets import toy_interaction
-
-X, y = toy_interaction(return_X_y=True)
-
-gam = LinearGAM(s(0, by=1)).fit(X, y)
-gam.summary()
-```
-
-#### Available Terms
-- `l()` linear terms
-- `s()` spline terms
-- `f()` factor terms
-- `te()` tensor products
-- `intercept`
-
-## Custom Models
-It's also easy to build custom models, by using the base **GAM** class and specifying the **distribution** and the **link function**.
-
-```python
-from pygam import GAM
-from pygam.datasets import trees
-
-X, y = trees(return_X_y=True)
-
-gam = GAM(distribution='gamma', link='log')
-gam.gridsearch(X, y)
-
-plt.scatter(y, gam.predict(X))
-plt.xlabel('true volume')
-plt.ylabel('predicted volume')
-```
-
-
-We can check the quality of the fit by looking at the `Pseudo R-Squared`:
-
-```
-gam.summary()
-
-GAM
-=============================================== ==========================================================
-Distribution: GammaDist Effective DoF: 25.3616
-Link Function: LogLink Log Likelihood: -26.1673
-Number of Samples: 31 AIC: 105.0579
- AICc: 501.5549
- GCV: 0.0088
- Scale: 0.001
- Pseudo R-Squared: 0.9993
-==========================================================================================================
-Feature Function Lambda Rank EDoF P > x Sig. Code
-================================= ==================== ============ ============ ============ ============
-s(0) [0.001] 20 2.04e-08 ***
-s(1) [0.001] 20 7.36e-06 ***
-intercept 0 1 4.39e-13 ***
-==========================================================================================================
-Significance codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
-```
-
-## Penalties / Constraints
-With GAMs we can encode **prior knowledge** and **control overfitting** by using penalties and constraints.
-
-#### Available penalties:
-- second derivative smoothing (default on numerical features)
-- L2 smoothing (default on categorical features)
-
-#### Availabe constraints:
-- monotonic increasing/decreasing smoothing
-- convex/concave smoothing
-- periodic smoothing [soon...]
-
-
-We can inject our intuition into our model by using **monotonic** and **concave** constraints:
-
-```python
-from pygam import LinearGAM, s
-from pygam.datasets import hepatitis
-
-X, y = hepatitis(return_X_y=True)
-
-gam1 = LinearGAM(s(0, constraints='monotonic_inc')).fit(X, y)
-gam2 = LinearGAM(s(0, constraints='concave')).fit(X, y)
-
-fig, ax = plt.subplots(1, 2)
-ax[0].plot(X, y, label='data')
-ax[0].plot(X, gam1.predict(X), label='monotonic fit')
-ax[0].legend()
-
-ax[1].plot(X, y, label='data')
-ax[1].plot(X, gam2.predict(X), label='concave fit')
-ax[1].legend()
-```
-
-
-## API
-pyGAM is intuitive, modular, and adheres to a familiar API:
-
-```python
-from pygam import LogisticGAM
-from pygam.datasets import toy_classification
-
-X, y = toy_classification(return_X_y=True)
-
-gam = LogisticGAM(s(0) + s(1) + s(2) + s(3) + s(4) + f(5))
-gam.fit(X, y)
-```
-
-Since GAMs are additive, it is also super easy to visualize each individual **feature function**, `f_i(X_i)`. These feature functions describe the effect of each `X_i` on `y` individually while marginalizing out all other predictors:
-
-```python
-pdeps = gam.partial_dependence(X)
-plt.plot(pdeps)
-```
-
-
-## Current Features
-### Models
-pyGAM comes with many models out-of-the-box:
-
-- GAM (base class for constructing custom models)
-- LinearGAM
-- LogisticGAM
-- GammaGAM
-- PoissonGAM
-- InvGaussGAM
-- ExpectileGAM
-
-You can mix and match distributions with link functions to create custom models!
-
-```python
-gam = GAM(distribution='gamma', link='inverse')
-```
-
-### Distributions
-
-- Normal
-- Binomial
-- Gamma
-- Poisson
-- Inverse Gaussian
-
-### Link Functions
-Link functions take the distribution mean to the linear prediction. These are the canonical link functions for the above distributions:
-
-- Identity
-- Logit
-- Inverse
-- Log
-- Inverse-squared
-
-### Callbacks
-Callbacks are performed during each optimization iteration. It's also easy to write your own.
-
-- deviance - model deviance
-- diffs - differences of coefficient norm
-- accuracy - model accuracy for LogisticGAM
-- coef - coefficient logging
-
-You can check a callback by inspecting:
-
-```python
-plt.plot(gam.logs_['deviance'])
-```
-
-
-### Linear Extrapolation
-
-
## Citing pyGAM
Please consider citing pyGAM if it has helped you in your research or work:
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 00000000..558d8ec3
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+SPHINXPROJ = pyGAM
+SOURCEDIR = source
+BUILDDIR = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
\ No newline at end of file
diff --git a/doc/make.bat b/doc/make.bat
new file mode 100644
index 00000000..3b02f276
--- /dev/null
+++ b/doc/make.bat
@@ -0,0 +1,36 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=.
+set BUILDDIR=_build
+set SPHINXPROJ=pyGAM
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.http://sphinx-doc.org/
+ exit /b 1
+)
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
+
+:end
+popd
diff --git a/doc/source/api/api.rst b/doc/source/api/api.rst
new file mode 100644
index 00000000..be834c91
--- /dev/null
+++ b/doc/source/api/api.rst
@@ -0,0 +1,38 @@
+.. Top level package
+
+User API
+========
+
+Generalized Additive Model Classes
+----------------------------------
+
+.. toctree::
+ :maxdepth: 2
+
+ gam
+ lineargam
+ gammagam
+ invgaussgam
+ logisticgam
+ poissongam
+ expectilegam
+
+
+Terms
+------
+
+Linear Term
+++++++++++++
+.. autofunction:: pygam.terms.l
+
+Spline Term
+++++++++++++
+.. autofunction:: pygam.terms.s
+
+Factor Term
+++++++++++++
+.. autofunction:: pygam.terms.f
+
+Tensor Term
+++++++++++++
+.. autofunction:: pygam.terms.te
diff --git a/doc/source/api/expectilegam.rst b/doc/source/api/expectilegam.rst
new file mode 100644
index 00000000..e947570d
--- /dev/null
+++ b/doc/source/api/expectilegam.rst
@@ -0,0 +1,48 @@
+.. Expectile GAM class documentation
+
+ExpectileGAM
+============
+::
+
+ from pygam import ExpectileGAM
+ from pygam.datasets import mcycle
+
+ X, y = mcycle(return_X_y=True)
+
+ # lets fit the mean model first by CV
+ gam50 = ExpectileGAM(expectile=0.5).gridsearch(X, y)
+
+ # and copy the smoothing to the other models
+ lam = gam50.lam
+
+ # now fit a few more models
+ gam95 = ExpectileGAM(expectile=0.95, lam=lam).fit(X, y)
+ gam75 = ExpectileGAM(expectile=0.75, lam=lam).fit(X, y)
+ gam25 = ExpectileGAM(expectile=0.25, lam=lam).fit(X, y)
+ gam05 = ExpectileGAM(expectile=0.05, lam=lam).fit(X, y)
+
+
+::
+
+ from matplotlib import pyplot as plt
+
+ XX = gam50.generate_X_grid(term=0, n=500)
+
+ plt.scatter(X, y, c='k', alpha=0.2)
+ plt.plot(XX, gam95.predict(XX), label='0.95')
+ plt.plot(XX, gam75.predict(XX), label='0.75')
+ plt.plot(XX, gam50.predict(XX), label='0.50')
+ plt.plot(XX, gam25.predict(XX), label='0.25')
+ plt.plot(XX, gam05.predict(XX), label='0.05')
+ plt.legend()
+
+
+.. image:: ../../../imgs/pygam_expectiles.png
+ :alt: pyGAM expectiles
+ :align: center
+
+.. autoclass:: pygam.pygam.ExpectileGAM
+ :members:
+ :inherited-members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/source/api/gam.rst b/doc/source/api/gam.rst
new file mode 100644
index 00000000..e81f7771
--- /dev/null
+++ b/doc/source/api/gam.rst
@@ -0,0 +1,9 @@
+.. Base GAM class documentation
+
+GAM
+===
+
+.. autoclass:: pygam.pygam.GAM
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/source/api/gammagam.rst b/doc/source/api/gammagam.rst
new file mode 100644
index 00000000..c56219cd
--- /dev/null
+++ b/doc/source/api/gammagam.rst
@@ -0,0 +1,10 @@
+.. Gamma GAM class documentation
+
+GammaGAM
+========
+
+.. autoclass:: pygam.pygam.GammaGAM
+ :members:
+ :inherited-members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/source/api/invgaussgam.rst b/doc/source/api/invgaussgam.rst
new file mode 100644
index 00000000..d6b817f4
--- /dev/null
+++ b/doc/source/api/invgaussgam.rst
@@ -0,0 +1,10 @@
+.. Inverse Gauss GAM class documentation
+
+InvGaussGAM
+===========
+
+.. autoclass:: pygam.pygam.InvGaussGAM
+ :members:
+ :inherited-members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/source/api/lineargam.rst b/doc/source/api/lineargam.rst
new file mode 100644
index 00000000..a3e971d8
--- /dev/null
+++ b/doc/source/api/lineargam.rst
@@ -0,0 +1,10 @@
+.. Linear GAM class documentation
+
+LinearGAM
+=========
+
+.. autoclass:: pygam.pygam.LinearGAM
+ :members:
+ :inherited-members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/source/api/logisticgam.rst b/doc/source/api/logisticgam.rst
new file mode 100644
index 00000000..2d6df7f1
--- /dev/null
+++ b/doc/source/api/logisticgam.rst
@@ -0,0 +1,10 @@
+.. Logistic GAM class documentation
+
+LogisticGAM
+===========
+
+.. autoclass:: pygam.pygam.LogisticGAM
+ :members:
+ :inherited-members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/source/api/poissongam.rst b/doc/source/api/poissongam.rst
new file mode 100644
index 00000000..9a4355e8
--- /dev/null
+++ b/doc/source/api/poissongam.rst
@@ -0,0 +1,10 @@
+.. Poisson GAM class documentation
+
+PoissonGAM
+==========
+
+.. autoclass:: pygam.pygam.PoissonGAM
+ :members:
+ :inherited-members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/source/api/terms.rst b/doc/source/api/terms.rst
new file mode 100644
index 00000000..a004a9b5
--- /dev/null
+++ b/doc/source/api/terms.rst
@@ -0,0 +1,20 @@
+.. Terms documentation
+
+Terms
+=========
+
+Linear Term
+------------
+.. autofunction:: pygam.terms.l
+
+Spline Term
+------------
+.. autofunction:: pygam.terms.s
+
+Factor Term
+------------
+.. autofunction:: pygam.terms.f
+
+Tensor Term
+------------
+.. autofunction:: pygam.terms.te
diff --git a/doc/source/conf.py b/doc/source/conf.py
new file mode 100644
index 00000000..766c2048
--- /dev/null
+++ b/doc/source/conf.py
@@ -0,0 +1,188 @@
+# -*- coding: utf-8 -*-
+#
+# Configuration file for the Sphinx documentation builder.
+#
+# This file does only contain a selection of the most common options. For a
+# full list see the documentation:
+# http://www.sphinx-doc.org/en/master/config
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+
+
+# -- Project information -----------------------------------------------------
+
+project = 'pyGAM'
+copyright = '2018, Daniel Servén and Charlie Brummitt'
+author = 'Daniel Servén and Charlie Brummitt'
+
+
+import pygam
+
+# The short X.Y version
+version = pygam.__version__
+# The full version, including alpha/beta/rc tags
+release = pygam.__version__
+
+
+# -- General configuration ---------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ 'sphinx.ext.autodoc',
+ 'sphinx.ext.doctest',
+ 'sphinx.ext.intersphinx',
+ 'sphinx.ext.todo',
+ 'sphinx.ext.coverage',
+ 'sphinx.ext.mathjax',
+ 'sphinx.ext.napoleon',
+ 'nbsphinx'
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+# source_suffix = ['.rst', '.md']
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path .
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '**.ipynb_checkpoints']
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'sphinx_rtd_theme'
+# html_theme = 'classic'
+#
+nbsphinx_prompt_width = 0
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#
+# html_theme_options = {
+# "fixed_sidebar": "false",
+# "description": "Generailzed Additive Models in Python"
+# }
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Custom sidebar templates, must be a dictionary that maps document names
+# to template names.
+#
+# The default sidebars (for documents that don't match any pattern) are
+# defined by theme itself. Builtin themes are using these templates by
+# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
+# 'searchbox.html']``.
+#
+# html_sidebars = {}
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+html_logo = '../../imgs/pygam_tensor.png'
+
+# -- Options for HTMLHelp output ---------------------------------------------
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'pyGAMdoc'
+
+
+# -- Options for LaTeX output ------------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ #
+ # 'papersize': 'letterpaper',
+
+ # The font size ('10pt', '11pt' or '12pt').
+ #
+ # 'pointsize': '10pt',
+
+ # Additional stuff for the LaTeX preamble.
+ #
+ # 'preamble': '',
+
+ # Latex figure (float) alignment
+ #
+ # 'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [
+ (master_doc, 'pyGAM.tex', 'pyGAM Documentation',
+ 'Daniel Servén', 'manual'),
+]
+
+
+# -- Options for manual page output ------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ (master_doc, 'pygam', 'pyGAM Documentation',
+ [author], 1)
+]
+
+
+# -- Options for Texinfo output ----------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ (master_doc, 'pyGAM', 'pyGAM Documentation',
+ author, 'pyGAM', 'One line description of project.',
+ 'Miscellaneous'),
+]
+
+
+# -- Extension configuration -------------------------------------------------
+
+# -- Options for intersphinx extension ---------------------------------------
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'https://docs.python.org/': None}
+
+# -- Options for todo extension ----------------------------------------------
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+todo_include_todos = True
diff --git a/doc/source/dev-api/api.rst b/doc/source/dev-api/api.rst
new file mode 100644
index 00000000..037c8675
--- /dev/null
+++ b/doc/source/dev-api/api.rst
@@ -0,0 +1,13 @@
+.. Top level package
+
+Developer API
+==============
+
+.. toctree::
+ :maxdepth: 2
+
+ terms
+ distributions
+ link
+ callbacks
+ penalties
diff --git a/doc/source/dev-api/callbacks.rst b/doc/source/dev-api/callbacks.rst
new file mode 100644
index 00000000..0da953f9
--- /dev/null
+++ b/doc/source/dev-api/callbacks.rst
@@ -0,0 +1,37 @@
+.. Callbacks documentation
+
+Callbacks
+=========
+
+.. autoclass:: pygam.callbacks.CallBack
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+
+.. autoclass:: pygam.callbacks.Accuracy
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+
+.. autoclass:: pygam.callbacks.Coef
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+
+.. autoclass:: pygam.callbacks.Deviance
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+
+.. autoclass:: pygam.callbacks.Diffs
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+.. autofunction:: pygam.callbacks.validate_callback
+
+.. autofunction:: pygam.callbacks.validate_callback_data
diff --git a/doc/source/dev-api/distributions.rst b/doc/source/dev-api/distributions.rst
new file mode 100644
index 00000000..e8d6acbf
--- /dev/null
+++ b/doc/source/dev-api/distributions.rst
@@ -0,0 +1,9 @@
+.. Base Link class documentation
+
+Distributions
+=============
+
+.. automodule:: pygam.distributions
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/source/dev-api/link.rst b/doc/source/dev-api/link.rst
new file mode 100644
index 00000000..56952dfd
--- /dev/null
+++ b/doc/source/dev-api/link.rst
@@ -0,0 +1,33 @@
+.. Base Link class documentation
+
+Links
+=====
+
+.. autoclass:: pygam.links.Link
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+
+.. autoclass:: pygam.links.IdentityLink
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+
+.. autoclass:: pygam.links.InvSquaredLink
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+
+.. autoclass:: pygam.links.LogitLink
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+
+.. autoclass:: pygam.links.LogLink
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/source/dev-api/penalties.rst b/doc/source/dev-api/penalties.rst
new file mode 100644
index 00000000..e202da8e
--- /dev/null
+++ b/doc/source/dev-api/penalties.rst
@@ -0,0 +1,9 @@
+.. Base Link class documentation
+
+Penalties
+=========
+
+.. automodule:: pygam.penalties
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/source/dev-api/terms.rst b/doc/source/dev-api/terms.rst
new file mode 100644
index 00000000..e85daa7e
--- /dev/null
+++ b/doc/source/dev-api/terms.rst
@@ -0,0 +1,43 @@
+.. Terms documentation
+
+Terms
+=========
+
+.. autoclass:: pygam.terms.Term
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+
+.. autoclass:: pygam.terms.LinearTerm
+ :members:
+ :undoc-members:
+ :show-inheritance:
+ :inherited-members:
+
+
+.. autoclass:: pygam.terms.SplineTerm
+ :members:
+ :undoc-members:
+ :show-inheritance:
+ :inherited-members:
+
+
+.. autoclass:: pygam.terms.FactorTerm
+ :members:
+ :undoc-members:
+ :show-inheritance:
+ :inherited-members:
+
+
+.. autoclass:: pygam.terms.TensorTerm
+ :members:
+ :undoc-members:
+ :show-inheritance:
+ :inherited-members:
+
+.. autoclass:: pygam.terms.TermList
+ :members:
+ :undoc-members:
+ :show-inheritance:
+ :inherited-members:
diff --git a/doc/source/index.rst b/doc/source/index.rst
new file mode 100644
index 00000000..80cbbb07
--- /dev/null
+++ b/doc/source/index.rst
@@ -0,0 +1,112 @@
+.. pyGAM documentation master file, created by
+ sphinx-quickstart on Sat Aug 18 15:42:53 2018.
+ You can adapt this file completely to your liking, but it should at least
+ contain the root `toctree` directive.
+
+Welcome to pyGAM's documentation!
+=================================
+
+.. image:: ../../imgs/pygam_tensor.png
+ :height: 300px
+ :alt: pyGAM logo
+ :align: center
+
+|Build Status| |Coverage| |PyPi Version| |Py27| |Py36| |Zenodo| |Open Source|
+
+pyGAM is a package for building Generalized Additive Models in Python,
+with an emphasis on modularity and performance. The API will be immediately familiar to anyone with experience
+of scikit-learn or scipy.
+
+Installation
+============
+
+pyGAM is on pypi, and can be installed using ``pip``: ::
+
+ pip install pygam
+
+Or via ``conda-forge``, however this is typically less up-to-date: ::
+
+ conda install -c conda-forge pyGAM
+
+You can install the bleeding edge from github using ``flit``.
+First clone the repo, ``cd`` into the main directory and do: ::
+
+ pip install flit
+ flit install
+
+
+Optional
+"""""""""
+To speed up optimization on large models with constraints, it helps to
+have ``scikit-sparse`` installed because it contains a slightly faster,
+sparse version of Cholesky factorization. The import from
+``scikit-sparse`` references ``nose``, so you'll need that too.
+
+The easiest way is to use Conda: ::
+
+ conda install -c conda-forge scikit-sparse nose
+
+
+More information is available in the `scikit-sparse docs
+`_.
+
+
+Dependencies
+=============
+pyGAM is tested on Python 2.7 and 3.6 and depends on ``NumPy``, ``SciPy``, and ``progressbar2`` (see ``requirements.txt`` for version information).
+
+Optional: ``scikit-sparse``.
+
+In addtion to the above dependencies, the ``datasets`` submodule relies on ``Pandas``.
+
+Citing pyGAM
+============
+
+ Servén D., Brummitt C. (2018). pyGAM: Generalized Additive Models in Python. Zenodo. `DOI: 10.5281/zenodo.1208723 `_
+
+Contact
+=======
+To report an issue with pyGAM please use the `issue tracker `_.
+
+License
+=======
+GNU General Public License v3.0
+
+
+Getting Started
+===============
+If you're new to pyGAM, read :ref:`the Tour of pyGAM `
+for an introduction to the package.
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Contents:
+
+ notebooks/quick_start.ipynb
+ notebooks/tour_of_pygam.ipynb
+ api/api
+ dev-api/api
+
+
+Indices and tables
+==================
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
+
+
+.. |Build Status| image:: https://travis-ci.org/dswah/pyGAM.svg?branch=master
+ :target: https://travis-ci.org/dswah/pyGAM
+.. |Coverage| image:: https://codecov.io/gh/dswah/pygam/branch/master/graph/badge.svg
+ :target: https://codecov.io/gh/dswah/pygam
+.. |PyPi Version| image:: https://badge.fury.io/py/pygam.svg
+ :target: https://badge.fury.io/py/pygam
+.. |Py27| image:: https://img.shields.io/badge/python-2.7-blue.svg
+ :target: https://badge.fury.io/py/pygam
+.. |Py36| image:: https://img.shields.io/badge/python-3.6-blue.svg
+ :target: https://badge.fury.io/py/pygam
+.. |Zenodo| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.1208723.svg
+ :target: https://doi.org/10.5281/zenodo.1208723
+.. |Open Source| image:: https://img.shields.io/badge/powered%20by-Open%20Source-orange.svg?style=flat&colorA=E1523D&colorB=007D8A
+ :target: https://github.com/dswah/pyGAM
diff --git a/doc/source/notebooks/pygam_basis.png b/doc/source/notebooks/pygam_basis.png
new file mode 100644
index 00000000..3159fd32
Binary files /dev/null and b/doc/source/notebooks/pygam_basis.png differ
diff --git a/doc/source/notebooks/quick_start.ipynb b/doc/source/notebooks/quick_start.ipynb
new file mode 100644
index 00000000..ee0921f7
--- /dev/null
+++ b/doc/source/notebooks/quick_start.ipynb
@@ -0,0 +1,532 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Quick Start\n",
+ "\n",
+ "This quick start will show how to do the following:\n",
+ "\n",
+ "- `Install` everything needed to use pyGAM.\n",
+ "- `fit a regression model` with custom terms\n",
+ "- search for the `best smoothing parameters`\n",
+ "- plot `partial dependence` functions\n",
+ "\n",
+ "\n",
+ "## Install pyGAM\n",
+ "#### Pip\n",
+ "\n",
+ " pip install pygam\n",
+ "\n",
+ "\n",
+ "#### Conda\n",
+ "pyGAM is on conda-forge, however this is typically less up-to-date:\n",
+ "\n",
+ " conda install -c conda-forge pygam\n",
+ " \n",
+ "\n",
+ "#### Bleeding edge\n",
+ "You can install the bleeding edge from github using `flit`.\n",
+ "First clone the repo, ``cd`` into the main directory and do:\n",
+ "\n",
+ " pip install flit\n",
+ " flit install"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Get `pandas` and `matplotlib`\n",
+ "\n",
+ " pip install pandas matplotlib\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Fit a Model\n",
+ "\n",
+ "Let's get to it. First we need some data:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/home/dswah/miniconda3/envs/pygam36/lib/python3.6/importlib/_bootstrap.py:219: RuntimeWarning: numpy.dtype size changed, may indicate binary incompatibility. Expected 96, got 88\n",
+ " return f(*args, **kwds)\n"
+ ]
+ }
+ ],
+ "source": [
+ "from pygam.datasets import wage\n",
+ "\n",
+ "X, y = wage()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now let's import a GAM that's made for regression problems.\n",
+ "\n",
+ "Let's fit a spline term to the first 2 features, and a factor term to the 3rd feature."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pygam import LinearGAM, s, f\n",
+ "\n",
+ "gam = LinearGAM(s(0) + s(1) + f(2)).fit(X, y)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Let's take a look at the model fit:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "LinearGAM \n",
+ "=============================================== ==========================================================\n",
+ "Distribution: NormalDist Effective DoF: 25.1911\n",
+ "Link Function: IdentityLink Log Likelihood: -24118.6847\n",
+ "Number of Samples: 3000 AIC: 48289.7516\n",
+ " AICc: 48290.2307\n",
+ " GCV: 1255.6902\n",
+ " Scale: 1236.7251\n",
+ " Pseudo R-Squared: 0.2955\n",
+ "==========================================================================================================\n",
+ "Feature Function Lambda Rank EDoF P > x Sig. Code \n",
+ "================================= ==================== ============ ============ ============ ============\n",
+ "s(0) [0.6] 20 7.1 5.95e-03 ** \n",
+ "s(1) [0.6] 20 14.1 1.11e-16 *** \n",
+ "f(2) [0.6] 5 4.0 1.11e-16 *** \n",
+ "intercept 1 0.0 1.11e-16 *** \n",
+ "==========================================================================================================\n",
+ "Significance codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n",
+ "\n",
+ "WARNING: Fitting splines and a linear function to a feature introduces a model identifiability problem\n",
+ " which can cause p-values to appear significant when they are not.\n",
+ "\n",
+ "WARNING: p-values calculated in this manner behave correctly for un-penalized models or models with\n",
+ " known smoothing parameters, but when smoothing parameters have been estimated, the p-values\n",
+ " are typically lower than they should be, meaning that the tests reject the null too readily.\n"
+ ]
+ }
+ ],
+ "source": [
+ "gam.summary()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Even though we have 3 terms with a total of `(20 + 20 + 5) = 45` free variables, the default smoothing penalty (`lam=0.6`) reduces the effective degrees of freedom to just ~25."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "By default, the spline terms, `s(...)`, use 20 basis functions. This is a good starting point. The rule of thumb is to use a fairly large amount of flexibility, and then let the smoothing penalty regularize the model.\n",
+ "\n",
+ "However, we can always use our expert knowledge to add flexibility where it is needed, or remove basis functions, and make fitting easier:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "gam = LinearGAM(s(0, n_splines=5) + s(1) + f(2)).fit(X, y)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Automatically tune the model"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "By default, spline terms, `s()` have a penalty on their 2nd derivative, which encourages the functions to be smoother, while factor terms, `f()` and linear terms `l()`, have a l2, ie ridge penalty, which encourages them to take on smaller values.\n",
+ "\n",
+ "`lam`, short for $\\lambda$, controls the strength of the regularization penalty on each term. Terms can have multiple penalties, and therefore multiple `lam`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[[0.6], [0.6], [0.6]]\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(gam.lam)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Our model has 3 `lam` parameters, currently just one per term.\n",
+ "\n",
+ "Let's perform a grid-search over multiple `lam` values to see if we can improve our model. \n",
+ "We will seek the model with the lowest generalized cross-validation (GCV) score.\n",
+ "\n",
+ "Our search space is 3-dimensional, so we have to be conservative with the number of points we consider per dimension.\n",
+ "\n",
+ "Let's try 5 values for each smoothing parameter, resulting in a total of `5*5*5 = 125` points in our grid."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "100% (125 of 125) |######################| Elapsed Time: 0:00:07 Time: 0:00:07\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "LinearGAM \n",
+ "=============================================== ==========================================================\n",
+ "Distribution: NormalDist Effective DoF: 9.2948\n",
+ "Link Function: IdentityLink Log Likelihood: -24119.7277\n",
+ "Number of Samples: 3000 AIC: 48260.0451\n",
+ " AICc: 48260.1229\n",
+ " GCV: 1244.089\n",
+ " Scale: 1237.1528\n",
+ " Pseudo R-Squared: 0.2915\n",
+ "==========================================================================================================\n",
+ "Feature Function Lambda Rank EDoF P > x Sig. Code \n",
+ "================================= ==================== ============ ============ ============ ============\n",
+ "s(0) [100000.] 5 2.0 7.54e-03 ** \n",
+ "s(1) [1000.] 20 3.3 1.11e-16 *** \n",
+ "f(2) [0.1] 5 4.0 1.11e-16 *** \n",
+ "intercept 1 0.0 1.11e-16 *** \n",
+ "==========================================================================================================\n",
+ "Significance codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n",
+ "\n",
+ "WARNING: Fitting splines and a linear function to a feature introduces a model identifiability problem\n",
+ " which can cause p-values to appear significant when they are not.\n",
+ "\n",
+ "WARNING: p-values calculated in this manner behave correctly for un-penalized models or models with\n",
+ " known smoothing parameters, but when smoothing parameters have been estimated, the p-values\n",
+ " are typically lower than they should be, meaning that the tests reject the null too readily.\n"
+ ]
+ }
+ ],
+ "source": [
+ "import numpy as np\n",
+ "\n",
+ "lam = np.logspace(-3, 5, 5)\n",
+ "lams = [lam] * 3\n",
+ "\n",
+ "gam.gridsearch(X, y, lam=lams)\n",
+ "gam.summary()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This is quite a bit better. Even though the in-sample $R^2$ value is lower, we can expect our model to generalize better because the GCV error is lower.\n",
+ "\n",
+ "We could be more rigorous by using a train/test split, and checking our model's error on the test set. We were also quite lazy and only tried 125 values in our hyperopt. We might find a better model if we spent more time searching across more points."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "For high-dimensional search-spaces, it is sometimes a good idea to try a **randomized search**. \n",
+ "We can acheive this by using numpy's `random` module:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "lams = np.random.rand(100, 3) # random points on [0, 1], with shape (100, 3)\n",
+ "lams = lams * 8 - 3 # shift values to -3, 3\n",
+ "lams = np.exp(lams) # transforms values to 1e-3, 1e3"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "100% (100 of 100) |######################| Elapsed Time: 0:00:07 Time: 0:00:07\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "LinearGAM \n",
+ "=============================================== ==========================================================\n",
+ "Distribution: NormalDist Effective DoF: 15.6683\n",
+ "Link Function: IdentityLink Log Likelihood: -24115.6727\n",
+ "Number of Samples: 3000 AIC: 48264.6819\n",
+ " AICc: 48264.8794\n",
+ " GCV: 1247.2011\n",
+ " Scale: 1235.4817\n",
+ " Pseudo R-Squared: 0.2939\n",
+ "==========================================================================================================\n",
+ "Feature Function Lambda Rank EDoF P > x Sig. Code \n",
+ "================================= ==================== ============ ============ ============ ============\n",
+ "s(0) [137.6336] 20 6.3 7.08e-03 ** \n",
+ "s(1) [128.3511] 20 5.4 1.11e-16 *** \n",
+ "f(2) [0.3212] 5 4.0 1.11e-16 *** \n",
+ "intercept 1 0.0 1.11e-16 *** \n",
+ "==========================================================================================================\n",
+ "Significance codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n",
+ "\n",
+ "WARNING: Fitting splines and a linear function to a feature introduces a model identifiability problem\n",
+ " which can cause p-values to appear significant when they are not.\n",
+ "\n",
+ "WARNING: p-values calculated in this manner behave correctly for un-penalized models or models with\n",
+ " known smoothing parameters, but when smoothing parameters have been estimated, the p-values\n",
+ " are typically lower than they should be, meaning that the tests reject the null too readily.\n"
+ ]
+ }
+ ],
+ "source": [
+ "random_gam = LinearGAM(s(0) + s(1) + f(2)).gridsearch(X, y, lam=lams)\n",
+ "random_gam.summary()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In this case, our deterministic search found a better model:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 18,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "gam.statistics_['GCV'] < random_gam.statistics_['GCV']"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The `statistics_` attribute is populated after the model has been fitted.\n",
+ "There are lots of interesting model statistics to check out, although many are automatically reported in the model summary:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "['n_samples',\n",
+ " 'm_features',\n",
+ " 'edof_per_coef',\n",
+ " 'edof',\n",
+ " 'scale',\n",
+ " 'cov',\n",
+ " 'se',\n",
+ " 'AIC',\n",
+ " 'AICc',\n",
+ " 'pseudo_r2',\n",
+ " 'GCV',\n",
+ " 'UBRE',\n",
+ " 'loglikelihood',\n",
+ " 'deviance',\n",
+ " 'p_values']"
+ ]
+ },
+ "execution_count": 19,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "list(gam.statistics_.keys())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Partial Dependence Functions"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "One of the most attractive properties of GAMs is that we can decompose and inspect the contribution of each feature to the overall prediction. \n",
+ "\n",
+ "This is done via **partial dependence** functions.\n",
+ "\n",
+ "Let's plot the partial dependence for each term in our model, along with a 95% confidence interval for the estimated function."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import matplotlib.pyplot as plt"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "metadata": {
+ "scrolled": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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\n",
+ "text/plain": [
+ "