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

Doctest #232

Merged
merged 40 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
7211153
added doctest testing abd fixed glm.py and solvers.py
BalzaniEdoardo Sep 24, 2024
66ad520
doctest as part of pytest run
BalzaniEdoardo Sep 25, 2024
52ae736
fixed all doctests
BalzaniEdoardo Sep 27, 2024
3d24f0c
Merge branch 'development' into doctest
BalzaniEdoardo Sep 27, 2024
7847551
added mpl
BalzaniEdoardo Sep 27, 2024
b006a5f
fixed test logic
BalzaniEdoardo Sep 27, 2024
95f244d
added basis test
BalzaniEdoardo Sep 27, 2024
10ed673
removed test path to src
BalzaniEdoardo Sep 27, 2024
bd92900
use repr for exceptions
BalzaniEdoardo Sep 27, 2024
5d1ca74
simplify pytest options
BalzaniEdoardo Sep 27, 2024
cd25819
added doctest to contributing
BalzaniEdoardo Sep 30, 2024
f985617
added quotations
BalzaniEdoardo Sep 30, 2024
9342b09
added tip
BalzaniEdoardo Sep 30, 2024
fcb47bf
fix syntax
BalzaniEdoardo Sep 30, 2024
eaeacbf
simplified example
BalzaniEdoardo Sep 30, 2024
1b60b75
added a doctest to contributing
BalzaniEdoardo Sep 30, 2024
0661368
improved text
BalzaniEdoardo Sep 30, 2024
a5fd71d
fix test
BalzaniEdoardo Sep 30, 2024
56f2ef5
indent
BalzaniEdoardo Sep 30, 2024
c0d25ea
indent
BalzaniEdoardo Sep 30, 2024
2314e94
indent fix
BalzaniEdoardo Sep 30, 2024
f69d7d9
indent fix x2
BalzaniEdoardo Sep 30, 2024
fdae612
add new lines to pass doctest
BalzaniEdoardo Sep 30, 2024
16dbe07
add pytest
BalzaniEdoardo Sep 30, 2024
0d13ba1
add double ```
BalzaniEdoardo Sep 30, 2024
ea3dd3b
indent
BalzaniEdoardo Sep 30, 2024
a33b457
indent the docstrings
BalzaniEdoardo Sep 30, 2024
f164085
do not test contributing (docstrings only works in modules)
BalzaniEdoardo Sep 30, 2024
278f9bc
add python
BalzaniEdoardo Sep 30, 2024
a7659d5
Merge pull request #233 from flatironinstitute/development
BalzaniEdoardo Sep 30, 2024
3599288
swap order
BalzaniEdoardo Oct 1, 2024
2488462
remove raw strings (r""") to docstrings whenever possible
BalzaniEdoardo Oct 1, 2024
c41b5a9
merge dev
BalzaniEdoardo Oct 1, 2024
dbb8d11
added support
BalzaniEdoardo Oct 1, 2024
9c42db7
added support in the README.md
BalzaniEdoardo Oct 1, 2024
79fcfab
Merge pull request #234 from flatironinstitute/add_support_section
BalzaniEdoardo Oct 1, 2024
0c6b03b
Merge branch 'main' into doctest
BalzaniEdoardo Oct 1, 2024
24da593
Update CONTRIBUTING.md
BalzaniEdoardo Oct 2, 2024
f09c2ef
edited CONTRIBUTING.md
BalzaniEdoardo Oct 2, 2024
9b1bb78
Merge branch 'doctest' of github.com:flatironinstitute/nemos into doc…
BalzaniEdoardo Oct 2, 2024
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
122 changes: 87 additions & 35 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ Lastly, you should make sure that the existing tests all run successfully and th
```bash
# run tests and make sure they all pass
pytest tests/

# run doctest (run all examples in docstrings and match output)
pytest --doctest-modules src/nemos/

# format the code base
black src/
isort src
Expand Down Expand Up @@ -184,38 +188,86 @@ properly documented as outlined below.

#### Adding documentation

1) **Docstrings**

All public-facing functions and classes should have complete docstrings, which start with a one-line short summary of the function,
a medium-length description of the function / class and what it does, and a complete description of all arguments and return values.
Math should be included in a `Notes` section when necessary to explain what the function is doing, and references to primary literature
should be included in a `References` section when appropriate. Docstrings should be relatively short, providing the information necessary
for a user to use the code.

Private functions and classes should have sufficient explanation that other developers know what the function / class does and how to use it,
but do not need to be as extensive.

We follow the [numpydoc](https://numpydoc.readthedocs.io/en/latest/) conventions for docstring structure.

2) **Examples/Tutorials**

If your changes are significant (add a new functionality or drastically change the current codebase), then the current examples may need to be updated or
a new example may need to be added.

All examples live within the `docs/` subfolder of `nemos`. These are written as `.py` files but are converted to
notebooks by [`mkdocs-gallery`](https://smarie.github.io/mkdocs-gallery/), and have a special syntax, as demonstrated in this [example
gallery](https://smarie.github.io/mkdocs-gallery/generated/gallery/).

We avoid using `.ipynb` notebooks directly because their JSON-based format makes them difficult to read, interpret, and resolve merge conflicts in version control.

To see if changes you have made break the current documentation, you can build the documentation locally.

```bash
# Clear the cached documentation pages
# This step is only necessary if your changes affected the src/ directory
rm -r docs/generated
# build the docs within the nemos repo
mkdocs build
```

If the build fails, you will see line-specific errors that prompted the failure.
1. **Docstrings**

All public-facing functions and classes should have complete docstrings, which start with a one-line short summary of the function, a medium-length description of the function/class and what it does, a complete description of all arguments and return values, and an example to illustrate usage. Math should be included in a `Notes` section when necessary to explain what the function is doing, and references to primary literature should be included in a `References` section when appropriate. Docstrings should be relatively short, providing the information necessary for a user to use the code.

Private functions and classes should have sufficient explanation that other developers know what the function/class does and how to use it, but do not need to be as extensive.

We follow the [numpydoc](https://numpydoc.readthedocs.io/en/latest/) conventions for docstring structure.

2. **Examples/Tutorials**

If your changes are significant (add a new functionality or drastically change the current codebase), then the current examples may need to be updated or a new example may need to be added.

All examples live within the `docs/` subfolder of `nemos`. These are written as `.py` files but are converted to notebooks by [`mkdocs-gallery`](https://smarie.github.io/mkdocs-gallery/), and have a special syntax, as demonstrated in this [example gallery](https://smarie.github.io/mkdocs-gallery/generated/gallery/).

We avoid using `.ipynb` notebooks directly because their JSON-based format makes them difficult to read, interpret, and resolve merge conflicts in version control.

To see if changes you have made break the current documentation, you can build the documentation locally.

```
# Clear the cached documentation pages
# This step is only necessary if your changes affected the src/ directory
rm -r docs/generated
# build the docs within the nemos repo
mkdocs build
```

If the build fails, you will see line-specific errors that prompted the failure.

3. **Doctest: Test the example code in your docs**

Doctests are a great way to ensure that code examples in your documentation remain accurate as the codebase evolves. With doctests, we will test any docstrings, Markdown files, or any other text-based documentation that contains code formatted as interactive Python sessions.

- **Docstrings:**
To include doctests in your function and class docstrings you must add an `Examples` section. The examples should be formatted as if you were typing them into a Python interactive session, with `>>>` used to indicate commands and expected outputs listed immediately below.

```python
def add(a, b):
"""
The sum of two numbers.

...Other docstrings sections (Parameters, Returns...)

Examples
--------
An expected output is required.
>>> add(1, 2)
3

Unless the output is captured.
>>> out = add(1, 2)

"""
return a + b
```

To validate all your docstrings examples, run pytest `--doctest-module` flag,

```
pytest --doctest-modules src/nemos/
```

This test is part of the Continuous Integration, every example must pass before we can merge a PR.

- **Documentation Pages:**
Doctests can also be included in Markdown files by using code blocks with the `python` language identifier and interactive Python examples. To enable this functionality, ensure that code blocks follow the standard Python doctest format:

```markdown
```python
>>> # Add any code
>>> x = 3 ** 2
>>> x + 1
10

```
```

To run doctests on a text file, use the following command:

```
python -m doctest -v path-to-your-text-file/file_name.md
```

All MarkDown files will be tested as part of the Continuous Integration.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,8 @@ We communicate via several channels on Github:
In all cases, we request that you respect our [code of
conduct](CODE_OF_CONDUCT.md).

## Support

This package is supported by the Center for Computational Neuroscience, in the Flatiron Institute of the Simons Foundation.

<img src="docs/assets/CCN-logo-wText.png" width="20%" alt="Flatiron Center for Computational Neuroscience logo.">
Binary file added docs/assets/CCN-logo-wText.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions docs/how_to_guide/plot_04_population_glm.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@
"""

import jax.numpy as jnp
import nemos as nmo
import numpy as np
import matplotlib.pyplot as plt
import numpy as np

import nemos as nmo

np.random.seed(123)

Expand Down
5 changes: 3 additions & 2 deletions docs/how_to_guide/plot_05_batch_glm.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@

"""

import matplotlib.pyplot as plt
import numpy as np
import pynapple as nap

import nemos as nmo
import numpy as np
import matplotlib.pyplot as plt

nap.nap_config.suppress_conversion_warnings = True

Expand Down
9 changes: 4 additions & 5 deletions docs/how_to_guide/plot_06_sklearn_pipeline_cv_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,20 +71,19 @@
# ## Combining basis transformations and GLM in a pipeline
# Let's start by creating some toy data.

import nemos as nmo
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import scipy.stats
import matplotlib.pyplot as plt
import seaborn as sns

from sklearn.pipeline import Pipeline
from sklearn.model_selection import GridSearchCV
from sklearn.pipeline import Pipeline

import nemos as nmo

# some helper plotting functions
from nemos import _documentation_utils as doc_plots


# predictors, shape (n_samples, n_features)
X = np.random.uniform(low=0, high=1, size=(1000, 1))
# observed counts, shape (n_samples,)
Expand Down
7 changes: 7 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,10 @@ We provide a **Poisson GLM** for analyzing spike counts, and a **Gamma GLM** for
## :material-scale-balance:{ .lg } License

Open source, [licensed under MIT](https://github.com/flatironinstitute/nemos/blob/main/LICENSE).


## Support

This package is supported by the Center for Computational Neuroscience, in the Flatiron Institute of the Simons Foundation.

<img src="assets/CCN-logo-wText.png" width="20%" alt="Flatiron Center for Computational Neuroscience logo.">
6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ dev = [
"pytest-cov", # Test coverage plugin for pytest
"statsmodels", # Used to compare model pseudo-r2 in testing
"scikit-learn", # Testing compatibility with CV & pipelines
"matplotlib>=3.7", # Needed by doctest to run docstrings examples
"pooch", # Required by doctest for fetch module
"dandi", # Required by doctest for fetch module
"seaborn", # Required by doctest for _documentation_utils module
]
docs = [
"mkdocs", # Documentation generator
Expand Down Expand Up @@ -112,7 +116,7 @@ testpaths = ["tests"] # Specify the directory where test files are l
[tool.coverage.run]
omit = [
"src/nemos/fetch/*",
"src/nemos/_documentation_utils/*"
"src/nemos/_documentation_utils/*",
]

[tool.coverage.report]
Expand Down
49 changes: 28 additions & 21 deletions src/nemos/basis.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,15 +152,15 @@ class TransformerBasis:
>>> # transformer can be used in pipelines
>>> transformer = TransformerBasis(basis)
>>> pipeline = Pipeline([ ("compute_features", transformer), ("glm", GLM()),])
>>> pipeline.fit(x[:, None], y) # x need to be 2D for sklearn transformer API
>>> print(pipeline.predict(np.random.normal(size=(10, 1)))) # predict rate from new data

>>> pipeline = pipeline.fit(x[:, None], y) # x need to be 2D for sklearn transformer API
>>> out = pipeline.predict(np.arange(10)[:, None]) # predict rate from new datas
>>> # TransformerBasis parameter can be cross-validated.
>>> # 5-fold cross-validate the number of basis
>>> param_grid = dict(compute_features__n_basis_funcs=[4, 10])
>>> grid_cv = GridSearchCV(pipeline, param_grid, cv=5)
>>> grid_cv.fit(x[:, None], y)
>>> grid_cv = grid_cv.fit(x[:, None], y)
>>> print("Cross-validated number of basis:", grid_cv.best_params_)
Cross-validated number of basis: {'compute_features__n_basis_funcs': 10}
"""

def __init__(self, basis: Basis):
Expand Down Expand Up @@ -289,7 +289,7 @@ def __getattr__(self, name: str):
return getattr(self._basis, name)

def __setattr__(self, name: str, value) -> None:
"""
r"""
Allow setting _basis or the attributes of _basis with a convenient dot assignment syntax.

Setting any other attribute is not allowed.
Expand All @@ -312,10 +312,11 @@ def __setattr__(self, name: str, value) -> None:
>>> # allowed
>>> trans_bas.n_basis_funcs = 20
>>> # not allowed
>>> tran_bas.random_attribute_name = "some value"
Traceback (most recent call last):
...
ValueError: Only setting _basis or existing attributes of _basis is allowed.
>>> try:
... trans_bas.random_attribute_name = "some value"
... except ValueError as e:
... print(repr(e))
ValueError('Only setting _basis or existing attributes of _basis is allowed.')
"""
# allow self._basis = basis
if name == "_basis":
Expand Down Expand Up @@ -343,7 +344,7 @@ def __sklearn_clone__(self) -> TransformerBasis:
return cloned_obj

def set_params(self, **parameters) -> TransformerBasis:
"""
r"""
Set TransformerBasis parameters.

When used with `sklearn.model_selection`, users can set either the `_basis` attribute directly
Expand All @@ -357,12 +358,16 @@ def set_params(self, **parameters) -> TransformerBasis:

>>> # setting parameters of _basis is allowed
>>> print(transformer_basis.set_params(n_basis_funcs=8).n_basis_funcs)

8
>>> # setting _basis directly is allowed
>>> print(transformer_basis.set_params(_basis=BSplineBasis(10))._basis)

>>> print(type(transformer_basis.set_params(_basis=BSplineBasis(10))._basis))
<class 'nemos.basis.BSplineBasis'>
>>> # mixing is not allowed, this will raise an exception
>>> transformer_basis.set_params(_basis=BSplineBasis(10), n_basis_funcs=2)
>>> try:
... transformer_basis.set_params(_basis=BSplineBasis(10), n_basis_funcs=2)
... except ValueError as e:
... print(repr(e))
ValueError('Set either new _basis object or parameters for existing _basis, not both.')
"""
new_basis = parameters.pop("_basis", None)
if new_basis is not None:
Expand Down Expand Up @@ -996,8 +1001,8 @@ def to_transformer(self) -> TransformerBasis:
>>> from sklearn.pipeline import Pipeline
>>> from sklearn.model_selection import GridSearchCV
>>> # load some data
>>> X, y = ... # X: features, y: neural activity
>>> basis = nmo.basis.RaisedCosineBasisLinear(10)
>>> X, y = np.random.normal(size=(30, 1)), np.random.poisson(size=30)
>>> basis = nmo.basis.RaisedCosineBasisLinear(10).to_transformer()
>>> glm = nmo.glm.GLM(regularizer="Ridge")
>>> pipeline = Pipeline([("basis", basis), ("glm", glm)])
>>> param_grid = dict(
Expand All @@ -1009,7 +1014,7 @@ def to_transformer(self) -> TransformerBasis:
... param_grid=param_grid,
... cv=5,
... )
>>> gridsearch.fit(X, y)
>>> gridsearch = gridsearch.fit(X, y)
"""

return TransformerBasis(copy.deepcopy(self))
Expand Down Expand Up @@ -1346,7 +1351,7 @@ def _check_n_basis_min(self) -> None:


class MSplineBasis(SplineBasis):
r"""
"""
M-spline[$^{[1]}$](#references) basis functions for modeling and data transformation.

M-splines are a type of spline basis function used for smooth curve fitting
Expand Down Expand Up @@ -1502,12 +1507,14 @@ def evaluate_on_grid(self, n_samples: int) -> Tuple[NDArray, NDArray]:
>>> mspline_basis = MSplineBasis(n_basis_funcs=4, order=3)
>>> sample_points, basis_values = mspline_basis.evaluate_on_grid(100)
>>> for i in range(4):
... plt.plot(sample_points, basis_values[:, i], label=f'Function {i+1}')
... p = plt.plot(sample_points, basis_values[:, i], label=f'Function {i+1}')
>>> plt.title('M-Spline Basis Functions')
Text(0.5, 1.0, 'M-Spline Basis Functions')
>>> plt.xlabel('Domain')
Text(0.5, 0, 'Domain')
>>> plt.ylabel('Basis Function Value')
>>> plt.legend()
>>> plt.show()
Text(0, 0.5, 'Basis Function Value')
>>> l = plt.legend()
"""
return super().evaluate_on_grid(n_samples)

Expand Down
3 changes: 1 addition & 2 deletions src/nemos/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,5 @@ class NotFittedError(ValueError, AttributeError):
... GLM().predict([[[1, 2], [2, 3], [3, 4]]])
... except NotFittedError as e:
... print(repr(e))
... # NotFittedError("This GLM instance is not fitted yet. Call 'fit' with
... # appropriate arguments.")
NotFittedError("This GLM instance is not fitted yet. Call 'fit' with appropriate arguments.")
"""
13 changes: 12 additions & 1 deletion src/nemos/fetch/fetch_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,21 @@ def download_dandi_data(dandiset_id: str, filepath: str) -> NWBHDF5IO:
Examples
--------
>>> import nemos as nmo
>>> import pynapple as nap
>>> io = nmo.fetch.download_dandi_data("000582",
"sub-11265/sub-11265_ses-07020602_behavior+ecephys.nwb")
... "sub-11265/sub-11265_ses-07020602_behavior+ecephys.nwb")
>>> nwb = nap.NWBFile(io.read(), lazy_loading=False)
>>> print(nwb)
07020602
┍━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━┑
│ Keys │ Type │
┝━━━━━━━━━━━━━━━━━━━━━┿━━━━━━━━━━┥
│ units │ TsGroup │
│ ElectricalSeriesLFP │ Tsd │
│ SpatialSeriesLED2 │ TsdFrame │
│ SpatialSeriesLED1 │ TsdFrame │
│ ElectricalSeries │ Tsd │
┕━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━┙

"""
if dandi is None:
Expand Down
Loading
Loading