# PyNumDiff
Python methods for numerical differentiation of noisy data, including multi-objective optimization routines for automated parameter selection.
<p align="center">
<a href="https://pynumdiff.readthedocs.io/master/">
<img alt="Python for Numerical Differentiation of noisy time series data" src="https://raw.githubusercontent.com/florisvb/PyNumDiff/master/logo.png" width="300" height="200" />
</a>
</p>
<p align="center">
<img src='https://github.com/florisvb/pynumdiff/actions/workflows/test.yml/badge.svg'/>
<a href='https://pynumdiff.readthedocs.io/master/'>
<img src='https://app.readthedocs.org/projects/pynumdiff/badge/?version=master' alt='Documentation Status' /></a>
<a href='https://coveralls.io/github/florisvb/PyNumDiff?branch=master'>
<img src='https://coveralls.io/repos/github/florisvb/PyNumDiff/badge.svg?branch=master' alt='Coverage Status' /></a>
<a href="https://badge.fury.io/py/pynumdiff">
<img src="https://badge.fury.io/py/pynumdiff.svg" alt="PyPI"></a>
<a href="https://zenodo.org/badge/latestdoi/159711175">
<img src="https://zenodo.org/badge/159711175.svg" alt="DOI"></a>
<a href="https://joss.theoj.org/papers/102257ee4b0142bf49bc18d7c810e9d5">
<img src="https://joss.theoj.org/papers/102257ee4b0142bf49bc18d7c810e9d5/status.svg"></a>
</p>
## Introduction
PyNumDiff is a Python package that implements various methods for computing numerical derivatives of noisy data, which can be a critical step in developing dynamic models or designing control. There are seven different families of methods implemented in this repository:
1. convolutional smoothing followed by finite difference calculation
2. polynomial fit methods
3. basis function fit methods
4. iterated finite differencing
5. total variation regularization of a finite difference derivative
6. Kalman (RTS) smoothing
7. local approximation with linear model
Most of these methods have multiple parameters, so we take a principled approach and propose a multi-objective optimization framework for choosing parameters that minimize a loss function to balance the faithfulness and smoothness of the derivative estimate. For more details, refer to [this paper](https://doi.org/10.1109/ACCESS.2020.3034077).
## Installing
Dependencies are listed in [pyproject.toml](https://github.com/florisvb/PyNumDiff/blob/master/pyproject.toml). They include the usual suspects like `numpy` and `scipy`, but also optionally `cvxpy`.
The code is compatible with >=Python 3.10. Install from PyPI with `pip install pynumdiff`, from source with `pip install git+https://github.com/florisvb/PyNumDiff`, or from local download with `pip install .`. Call `pip install pynumdiff[advanced]` to automatically install optional dependencies from the advanced list, like [CVXPY](https://www.cvxpy.org).
## Usage
For more details, read our [Sphinx documentation](https://pynumdiff.readthedocs.io/master/). The basic pattern of all differentiation methods is:
```python
somethingdiff(x, dt, **kwargs)
```
where `x` is data, `dt` is a step size, and various keyword arguments control the behavior. Some methods support variable step size, in which case the second parameter is renamed `_t` and can receive either a constant step size or an array of values to denote sample locations.
You can provide the parameters:
```python
from pynumdiff.submodule import method
x_hat, dxdt_hat = method(x, dt, param1=val1, param2=val2, ...)
```
Or you can find parameter by calling the multi-objective optimization algorithm from the `optimize` module:
```python
from pynumdiff.optimize import optimize
# estimate cutoff_frequency by (a) counting the number of true peaks per second in the data or (b) look at power spectra and choose cutoff
tvgamma = np.exp(-1.6*np.log(cutoff_frequency) -0.71*np.log(dt) - 5.1) # see https://ieeexplore.ieee.org/abstract/document/9241009
params, val = optimize(somethingdiff, x, dt, tvgamma=tvgamma, # smoothness hyperparameter which defaults to None if dxdt_truth given
dxdt_truth=None, # give ground truth data if available, in which case tvgamma goes unused
search_space_updates={'param1':[vals], 'param2':[vals], ...})
print('Optimal parameters: ', params)
x_hat, dxdt_hat = somethingdiff(x, dt, **params)
```
If no `search_space_updates` is given, a default search space is used. See the top of `_optimize.py`.
The following heuristic works well for choosing `tvgamma`, where `cutoff_frequency` is the highest frequency content of the signal in your data, and `dt` is the timestep: `tvgamma=np.exp(-1.6*np.log(cutoff_frequency)-0.71*np.log(dt)-5.1)`. Larger values of `tvgamma` produce smoother derivatives. The value of `tvgamma` is largely universal across methods, making it easy to compare method results. Be aware the optimization is a fairly heavy process.
### Notebook examples
Much more extensive usage is demonstrated in Jupyter notebooks:
* Differentiation with different methods: [1_basic_tutorial.ipynb](https://github.com/florisvb/PyNumDiff/blob/master/examples/1_basic_tutorial.ipynb)
* Parameter Optimization with known ground truth (only for demonstration purpose): [2a_optimizing_parameters_with_dxdt_known.ipynb](https://github.com/florisvb/PyNumDiff/blob/master/examples/2a_optimizing_parameters_with_dxdt_known.ipynb)
* Parameter Optimization with unknown ground truth: [2b_optimizing_parameters_with_dxdt_unknown.ipynb](https://github.com/florisvb/PyNumDiff/blob/master/examples/2b_optimizing_parameters_with_dxdt_unknown.ipynb)
* Automatic method suggestion: [3_automatic_method_suggestion.ipynb](https://github.com/florisvb/PyNumDiff/blob/master/examples/3_automatic_method_suggestion.ipynb)
## Repo Structure
- `.github/workflows` contains `.yaml` that configures our GitHub Actions continuous integration (CI) runs.
- `docs/` contains `make` files and `.rst` files to govern the way `sphinx` builds documentation, either locally by navigating to this folder and calling `make html` or in the cloud by `readthedocs.io`.
- `examples/` contains Jupyter notebooks that demonstrate some usage of the library.
- `pynumdiff/` contains the source code. For a full list of modules and further navigation help, see the readme in this subfolder.
- `.editorconfig` ensures tabs are displayed as 4 characters wide.
- `.gitignore` ensures files generated by local `pip install`s, Jupyter notebook runs, caches from code runs, virtual environments, and more are not picked up by `git` and accidentally added to the repo.
- `.pylintrc` configures `pylint`, a tool for autochecking code quality.
- `.readthedocs.yaml` configures `readthedocs` and is necessary for documentation to get auto-rebuilt.
- `CITATION.cff` is citation information for the Journal of Open-Source Software (JOSS) paper associated with this project.
- `LICENSE.txt` allows free usage of this project.
- `README.md` is the text you're reading, hello.
- `linting.py` is a script to run `pylint`.
- `pyproject.toml` governs how this package is set up and installed, including dependencies.
## Citation
See CITATION.cff file as well as the following references.
### PyNumDiff python package:
@article{PyNumDiff2022,
doi = {10.21105/joss.04078},
url = {https://doi.org/10.21105/joss.04078},
year = {2022},
publisher = {The Open Journal},
volume = {7},
number = {71},
pages = {4078},
author = {Floris van Breugel and Yuying Liu and Bingni W. Brunton and J. Nathan Kutz},
title = {PyNumDiff: A Python package for numerical differentiation of noisy time-series data},
journal = {Journal of Open Source Software}
}
### Optimization algorithm:
@article{ParamOptimizationDerivatives2020,
doi={10.1109/ACCESS.2020.3034077}
author={F. {van Breugel} and J. {Nathan Kutz} and B. W. {Brunton}},
journal={IEEE Access},
title={Numerical differentiation of noisy data: A unifying multi-objective optimization framework},
year={2020}
}
## Running the tests
We are using GitHub Actions for continuous intergration testing.
Run tests locally by navigating to the repo in a terminal and calling
```bash
> pytest -s
```
Add the flag `--plot` to see plots of the methods against test functions. Add the flag `--bounds` to print $\log$ error bounds (useful when changing method behavior).
## License
This project utilizes the [MIT LICENSE](https://github.com/florisvb/PyNumDiff/blob/master/LICENSE.txt).
100% open-source, feel free to utilize the code however you like.
Raw data
{
"_id": null,
"home_page": null,
"name": "pynumdiff",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": "Floris van Breugel <fvanbreugel@unr.edu>, Pavel Komarov <pvlkmrv@uw.edu>, Yuying Liu <yliu814@uw.edu>",
"keywords": "derivative, smoothing, curve fitting, optimization, total variation",
"author": null,
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/a6/a2/0b3fa9974a5737ad3005b68e031196e7e3a94abeb592f143a7587d9b1098/pynumdiff-0.2.tar.gz",
"platform": null,
"description": "# PyNumDiff\n\nPython methods for numerical differentiation of noisy data, including multi-objective optimization routines for automated parameter selection.\n\n<p align=\"center\">\n <a href=\"https://pynumdiff.readthedocs.io/master/\">\n <img alt=\"Python for Numerical Differentiation of noisy time series data\" src=\"https://raw.githubusercontent.com/florisvb/PyNumDiff/master/logo.png\" width=\"300\" height=\"200\" />\n </a>\n</p>\n\n<p align=\"center\">\n <img src='https://github.com/florisvb/pynumdiff/actions/workflows/test.yml/badge.svg'/>\n <a href='https://pynumdiff.readthedocs.io/master/'>\n <img src='https://app.readthedocs.org/projects/pynumdiff/badge/?version=master' alt='Documentation Status' /></a>\n <a href='https://coveralls.io/github/florisvb/PyNumDiff?branch=master'>\n <img src='https://coveralls.io/repos/github/florisvb/PyNumDiff/badge.svg?branch=master' alt='Coverage Status' /></a>\n <a href=\"https://badge.fury.io/py/pynumdiff\">\n <img src=\"https://badge.fury.io/py/pynumdiff.svg\" alt=\"PyPI\"></a>\n <a href=\"https://zenodo.org/badge/latestdoi/159711175\">\n <img src=\"https://zenodo.org/badge/159711175.svg\" alt=\"DOI\"></a>\n <a href=\"https://joss.theoj.org/papers/102257ee4b0142bf49bc18d7c810e9d5\">\n <img src=\"https://joss.theoj.org/papers/102257ee4b0142bf49bc18d7c810e9d5/status.svg\"></a>\n</p>\n\n## Introduction\n\nPyNumDiff is a Python package that implements various methods for computing numerical derivatives of noisy data, which can be a critical step in developing dynamic models or designing control. There are seven different families of methods implemented in this repository:\n\n1. convolutional smoothing followed by finite difference calculation\n2. polynomial fit methods\n3. basis function fit methods\n4. iterated finite differencing\n5. total variation regularization of a finite difference derivative\n6. Kalman (RTS) smoothing\n7. local approximation with linear model\n\nMost of these methods have multiple parameters, so we take a principled approach and propose a multi-objective optimization framework for choosing parameters that minimize a loss function to balance the faithfulness and smoothness of the derivative estimate. For more details, refer to [this paper](https://doi.org/10.1109/ACCESS.2020.3034077).\n\n## Installing\n\nDependencies are listed in [pyproject.toml](https://github.com/florisvb/PyNumDiff/blob/master/pyproject.toml). They include the usual suspects like `numpy` and `scipy`, but also optionally `cvxpy`.\n\nThe code is compatible with >=Python 3.10. Install from PyPI with `pip install pynumdiff`, from source with `pip install git+https://github.com/florisvb/PyNumDiff`, or from local download with `pip install .`. Call `pip install pynumdiff[advanced]` to automatically install optional dependencies from the advanced list, like [CVXPY](https://www.cvxpy.org).\n\n## Usage\n\nFor more details, read our [Sphinx documentation](https://pynumdiff.readthedocs.io/master/). The basic pattern of all differentiation methods is:\n\n```python\nsomethingdiff(x, dt, **kwargs)\n```\n\nwhere `x` is data, `dt` is a step size, and various keyword arguments control the behavior. Some methods support variable step size, in which case the second parameter is renamed `_t` and can receive either a constant step size or an array of values to denote sample locations.\n\nYou can provide the parameters:\n```python\nfrom pynumdiff.submodule import method\n\nx_hat, dxdt_hat = method(x, dt, param1=val1, param2=val2, ...) \n```\n\nOr you can find parameter by calling the multi-objective optimization algorithm from the `optimize` module:\n```python\nfrom pynumdiff.optimize import optimize\n\n# estimate cutoff_frequency by (a) counting the number of true peaks per second in the data or (b) look at power spectra and choose cutoff\ntvgamma = np.exp(-1.6*np.log(cutoff_frequency) -0.71*np.log(dt) - 5.1) # see https://ieeexplore.ieee.org/abstract/document/9241009\n\nparams, val = optimize(somethingdiff, x, dt, tvgamma=tvgamma, # smoothness hyperparameter which defaults to None if dxdt_truth given\n dxdt_truth=None, # give ground truth data if available, in which case tvgamma goes unused\n search_space_updates={'param1':[vals], 'param2':[vals], ...})\n\nprint('Optimal parameters: ', params)\nx_hat, dxdt_hat = somethingdiff(x, dt, **params)\n```\nIf no `search_space_updates` is given, a default search space is used. See the top of `_optimize.py`.\n\nThe following heuristic works well for choosing `tvgamma`, where `cutoff_frequency` is the highest frequency content of the signal in your data, and `dt` is the timestep: `tvgamma=np.exp(-1.6*np.log(cutoff_frequency)-0.71*np.log(dt)-5.1)`. Larger values of `tvgamma` produce smoother derivatives. The value of `tvgamma` is largely universal across methods, making it easy to compare method results. Be aware the optimization is a fairly heavy process.\n\n### Notebook examples\n\nMuch more extensive usage is demonstrated in Jupyter notebooks:\n* Differentiation with different methods: [1_basic_tutorial.ipynb](https://github.com/florisvb/PyNumDiff/blob/master/examples/1_basic_tutorial.ipynb)\n* Parameter Optimization with known ground truth (only for demonstration purpose): [2a_optimizing_parameters_with_dxdt_known.ipynb](https://github.com/florisvb/PyNumDiff/blob/master/examples/2a_optimizing_parameters_with_dxdt_known.ipynb)\n* Parameter Optimization with unknown ground truth: [2b_optimizing_parameters_with_dxdt_unknown.ipynb](https://github.com/florisvb/PyNumDiff/blob/master/examples/2b_optimizing_parameters_with_dxdt_unknown.ipynb)\n* Automatic method suggestion: [3_automatic_method_suggestion.ipynb](https://github.com/florisvb/PyNumDiff/blob/master/examples/3_automatic_method_suggestion.ipynb)\n\n## Repo Structure\n\n- `.github/workflows` contains `.yaml` that configures our GitHub Actions continuous integration (CI) runs.\n- `docs/` contains `make` files and `.rst` files to govern the way `sphinx` builds documentation, either locally by navigating to this folder and calling `make html` or in the cloud by `readthedocs.io`.\n- `examples/` contains Jupyter notebooks that demonstrate some usage of the library.\n- `pynumdiff/` contains the source code. For a full list of modules and further navigation help, see the readme in this subfolder.\n- `.editorconfig` ensures tabs are displayed as 4 characters wide.\n- `.gitignore` ensures files generated by local `pip install`s, Jupyter notebook runs, caches from code runs, virtual environments, and more are not picked up by `git` and accidentally added to the repo.\n- `.pylintrc` configures `pylint`, a tool for autochecking code quality.\n- `.readthedocs.yaml` configures `readthedocs` and is necessary for documentation to get auto-rebuilt.\n- `CITATION.cff` is citation information for the Journal of Open-Source Software (JOSS) paper associated with this project.\n- `LICENSE.txt` allows free usage of this project.\n- `README.md` is the text you're reading, hello.\n- `linting.py` is a script to run `pylint`.\n- `pyproject.toml` governs how this package is set up and installed, including dependencies.\n\n## Citation\n\nSee CITATION.cff file as well as the following references.\n\n### PyNumDiff python package:\n\n @article{PyNumDiff2022,\n doi = {10.21105/joss.04078},\n url = {https://doi.org/10.21105/joss.04078},\n year = {2022},\n publisher = {The Open Journal},\n volume = {7},\n number = {71},\n pages = {4078},\n author = {Floris van Breugel and Yuying Liu and Bingni W. Brunton and J. Nathan Kutz},\n title = {PyNumDiff: A Python package for numerical differentiation of noisy time-series data},\n journal = {Journal of Open Source Software}\n }\n\n### Optimization algorithm:\n\n @article{ParamOptimizationDerivatives2020, \n doi={10.1109/ACCESS.2020.3034077}\n author={F. {van Breugel} and J. {Nathan Kutz} and B. W. {Brunton}}, \n journal={IEEE Access}, \n title={Numerical differentiation of noisy data: A unifying multi-objective optimization framework}, \n year={2020}\n }\n\n## Running the tests\n\nWe are using GitHub Actions for continuous intergration testing.\n\nRun tests locally by navigating to the repo in a terminal and calling\n```bash\n> pytest -s\n```\n\nAdd the flag `--plot` to see plots of the methods against test functions. Add the flag `--bounds` to print $\\log$ error bounds (useful when changing method behavior).\n\n## License\n\nThis project utilizes the [MIT LICENSE](https://github.com/florisvb/PyNumDiff/blob/master/LICENSE.txt).\n100% open-source, feel free to utilize the code however you like. \n",
"bugtrack_url": null,
"license": "MIT",
"summary": "pynumdiff: numerical derivatives in python",
"version": "0.2",
"project_urls": {
"documentation": "https://pynumdiff.readthedocs.io/",
"homepage": "https://github.com/florisvb/PyNumDiff",
"package": "https://pypi.org/project/pynumdiff/"
},
"split_keywords": [
"derivative",
" smoothing",
" curve fitting",
" optimization",
" total variation"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "6b6e84de64571be57eb4f1f4800aa5ff11a9e8c6c342a6cdd6e3be7405745764",
"md5": "6f7b82061bc234ee8fe700ce1d9973d0",
"sha256": "d53ed57cb7c7cdc80a3541e607f03d01aa94cfeca590faa939288ae803e36bcd"
},
"downloads": -1,
"filename": "pynumdiff-0.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "6f7b82061bc234ee8fe700ce1d9973d0",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 60101,
"upload_time": "2025-10-09T01:16:46",
"upload_time_iso_8601": "2025-10-09T01:16:46.964981Z",
"url": "https://files.pythonhosted.org/packages/6b/6e/84de64571be57eb4f1f4800aa5ff11a9e8c6c342a6cdd6e3be7405745764/pynumdiff-0.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "a6a20b3fa9974a5737ad3005b68e031196e7e3a94abeb592f143a7587d9b1098",
"md5": "8e5e6a76b13336e4671ad762cb2cf25e",
"sha256": "62e290df47febdc051c098c0c3492c3457651e039267b081c580afcb93456436"
},
"downloads": -1,
"filename": "pynumdiff-0.2.tar.gz",
"has_sig": false,
"md5_digest": "8e5e6a76b13336e4671ad762cb2cf25e",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 12145520,
"upload_time": "2025-10-09T01:16:48",
"upload_time_iso_8601": "2025-10-09T01:16:48.090849Z",
"url": "https://files.pythonhosted.org/packages/a6/a2/0b3fa9974a5737ad3005b68e031196e7e3a94abeb592f143a7587d9b1098/pynumdiff-0.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-09 01:16:48",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "florisvb",
"github_project": "PyNumDiff",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "pynumdiff"
}