Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add splines example #3

Merged
merged 1 commit into from
Nov 1, 2023
Merged
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Private files
private/

# Notebooks by default should not be uploaded (use Quarto Markdown as an alternative)
*.nb
*.ipynb
Expand Down
3 changes: 3 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# API Reference

::: covvfit.create_spline_matrix
25 changes: 25 additions & 0 deletions environment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: covvfit
channels:
- bioconda
- conda-forge
dependencies:
- arviz=0.16.1
- matplotlib-base=3.8.0
- numpy=1.25.2
- pandas=2.1.2
- pip=23.3.1
- pymc=5.9.1
- pymc-base=5.9.1
- pytensor=2.17.3
- pytensor-base=2.17.3
- pytest=7.4.3
- python=3.10.13
- pyyaml=6.0.1
- scipy=1.11.3
- setuptools=68.2.2
- snakemake=7.32.4
- snakemake-minimal=7.32.4
- xarray=2023.10.1
- xarray-einstats=0.6.0
- yaml=0.2.5

77 changes: 77 additions & 0 deletions examples/splines_demonstration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""Bayesian regression using B-splines."""
import matplotlib.pyplot as plt
import numpy as np
import pymc as pm

import covvfit as cv


def create_model(
xs: np.ndarray,
ys: np.ndarray,
n_coefs: int = 5,
degree: int = 3,
) -> pm.Model:
# Create the B-spline basis functions
pts = cv.create_spline_matrix(xs, n_coefs=n_coefs)

with pm.Model() as model:
# Weights are the coefficients of the B-spline basis functions
weights = pm.Normal("weights", 0, 10, size=(n_coefs, 1))

# The function is a linear combination of the basis functions
func = pm.Deterministic("func", (pts @ weights).ravel())

# We add normal noise with unknown standard deviation
sigma = pm.HalfCauchy("sigma", 1)
pm.Normal("observed", mu=func, sigma=sigma, observed=ys)

return model


def main() -> None:
rng = np.random.default_rng(42)

xs = np.linspace(0, 1, 151)
ys_perfect = 2 * xs + 3 * np.sin(5 * xs)
ys_obs = ys_perfect + 2 * rng.normal(size=xs.shape)

model = create_model(xs=xs, ys=ys_obs, n_coefs=5)

n_samples_per_chain = 500
n_chains = 4
thinning = 10
with model:
idata = pm.sample(tune=500, draws=n_samples_per_chain, chains=n_chains)
thinned_idata = idata.sel(draw=slice(None, None, thinning))
idata.extend(pm.sample_posterior_predictive(thinned_idata))

fig, ax = plt.subplots()

# Plot individual posterior predictive samples
post_pred = idata.posterior_predictive["observed"] # pyright: ignore

for chain in range(n_chains):
for sample in range(n_samples_per_chain // thinning):
ys = post_pred[chain, sample]
ax.plot(xs, ys, alpha=0.02, color="navy")

# Plot mean of posterior predictive
mean_predictive = post_pred.mean(axis=(0, 1))
ax.plot(xs, mean_predictive, color="navy", label="Posterior predictive mean")

# Plot "perfect data"
ax.plot(xs, ys_perfect, color="maroon", label="Noiseless data")
# Plot noisy data
ax.scatter(xs, ys_obs, c="k", s=3, label="Observed data")

ax.set_title("Bayesian regression using B-splines")
ax.set_xlabel("$x$")
ax.set_ylabel("$y$")

ax.legend(frameon=False)
fig.savefig("splines_demonstration.pdf")


if __name__ == "__main__":
main()
5 changes: 5 additions & 0 deletions src/covvfit/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
from covvfit._splines import create_spline_matrix

VERSION = "0.1.0"


__all__ = ["create_spline_matrix", "VERSION"]
44 changes: 44 additions & 0 deletions src/covvfit/_splines.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Spline functions utilities."""
from typing import Optional

import numpy as np
from scipy.interpolate import BSpline


def create_spline_matrix(
xs: np.ndarray,
n_coefs: int = 5,
degree: int = 3,
knots: Optional[np.ndarray] = None,
) -> np.ndarray:
"""Creates basis spline functions evaluated at points provided.

Args:
xs: points to evaluate the spline functions on, shape (N,)
n_coefs: number of coefficients
degree: B-spline degree
knots: knot positions, should be of shape `n_coefs + degree + 1`

Returns:
B-splines evaluated at the points provided. Shape (N, n_coefs)

Note:
The number of knots is given by `n_coefs + degree + 1`
"""
n_knots = n_coefs + degree + 1

if knots is None:
knots = np.linspace(np.min(xs), np.max(xs), n_knots)
else:
knots = np.asarray(knots)

assert isinstance(knots, np.ndarray)
assert knots.shape[0] == n_knots

def coeff(i: int) -> np.ndarray:
"""One-hot vector with 1 at position `i`"""
return np.eye(n_coefs)[i]

return np.vstack(
[BSpline(t=knots, c=coeff(i), k=degree)(xs) for i in range(n_coefs)]
).T
Loading