././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1633430830.6824074
csaps-1.1.0/ 0000777 0000000 0000000 00000000000 00000000000 010755 5 ustar 00 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633430776.0
csaps-1.1.0/CHANGELOG.md 0000666 0000000 0000000 00000007073 00000000000 012575 0 ustar 00 0000000 0000000 # Changelog
## v1.1.0
* Introduced optional `normalizedsmooth` argument to reduce dependence on xdata and weights [#47](https://github.com/espdev/csaps/pull/47)
* Update numpy and scipy dependency ranges
## v1.0.4 (04.05.2021)
* Bump numpy dependency version
## v1.0.3 (01.01.2021)
* Bump scipy dependency version
* Bump sphinx dependency version and use m2r2 sphinx extension instead of m2r
* Add Python 3.9 to classifiers list and to Travis CI
* Set development status classifier to "5 - Production/Stable"
* Happy New Year!
## v1.0.2 (19.07.2020)
* Fix using 'nu' argument when n-d grid spline evaluating [#32](https://github.com/espdev/csaps/pull/32)
## v1.0.1 (19.07.2020)
* Fix n-d grid spline evaluating performance regression [#31](https://github.com/espdev/csaps/pull/31)
## v1.0.0 (11.07.2020)
* Use `PPoly` and `NdPPoly` base classes from SciPy interpolate module for `SplinePPForm` and `NdGridSplinePPForm` respectively.
* Remove deprecated classes `UnivariateCubicSmoothingSpline` and `MultivariateCubicSmoothingSpline`
* Update the documentation
**Notes**
In this release the spline representation (the array of spline coefficients) has been changed
according to `PPoly`/`NdPPoly`.
See SciPy [PPoly](https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.PPoly.html)
and [NdPPoly](https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.NdPPoly.html) documentation for details.
## v0.11.0 (28.03.2020)
* Internal re-design `SplinePPForm` and `NdGridSplinePPForm` classes [#17](https://github.com/espdev/csaps/issues/17):
- Remove `shape` and `axis` properties and reshaping data in these classes
- `NdGridSplinePPForm` coefficients array for 1D grid now is 1-d instead of 2-d
* Refactoring the code and decrease memory consumption
* Add `overload` type-hints for `csaps` function signatures
## v0.10.1 (19.03.2020)
* Fix call of `numpy.pad` function for numpy <1.17 [#15](https://github.com/espdev/csaps/issues/15)
## v0.10.0 (18.02.2020)
* Significant performance improvements for make/evaluate splines and memory consumption optimization
* Change format for storing spline coefficients (reshape coeffs array) to improve performance
* Add shape property to `SplinePPForm`/`NdGridSplinePPForm` and axis property to `SplinePPForm`
* Fix issues with the smoothing factor in nd-grid case: inverted ordering and unnable to use 0.0 value
* Update documentation
## v0.9.0 (21.01.2020)
* Drop support of Python 3.5
* `weights`, `smooth` and `axis` arguments in `csaps` function are keyword-only now
* `UnivariateCubicSmoothingSpline` and `MultivariateCubicSmoothingSpline` classes are deprecated
and will be removed in 1.0.0 version. Use `CubicSmoothingSpline` instead.
## v0.8.0 (13.01.2020)
* Add `csaps` function that can be used as the main API
* Refactor the internal structure of the package
* Add the [documentation](https://csaps.readthedocs.io)
**Attention**
This is the last version that supports Python 3.5.
The next versions will support Python 3.6 or above.
## v0.7.0 (19.09.2019)
* Add Generic-based type-hints and mypy-compatibility
## v0.6.1 (13.09.2019)
* A slight refactoring and extra data copies removing
## v0.6.0 (12.09.2019)
* Add "axis" parameter for univariate/multivariate cases
## v0.5.0 (10.06.2019)
* Reorganize the project to package-based structure
* Add the interface class for all smoothing spline classes
## v0.4.2 (07.09.2019)
* FIX: "smooth" value is 0.0 was not used
## v0.4.1 (30.05.2019)
* First PyPI release
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1623967905.0
csaps-1.1.0/LICENSE 0000666 0000000 0000000 00000002105 00000000000 011760 0 ustar 00 0000000 0000000 MIT License
Copyright (c) 2017 Eugene Prilepin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1623967905.0
csaps-1.1.0/MANIFEST.in 0000666 0000000 0000000 00000000072 00000000000 012512 0 ustar 00 0000000 0000000 include README.md
include CHANGELOG.md
include LICENSE
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1633430830.6824074
csaps-1.1.0/PKG-INFO 0000666 0000000 0000000 00000022054 00000000000 012055 0 ustar 00 0000000 0000000 Metadata-Version: 2.1
Name: csaps
Version: 1.1.0
Summary: Cubic spline approximation (smoothing)
Home-page: https://github.com/espdev/csaps
Author: Eugene Prilepin
Author-email: esp.home@gmail.com
License: MIT
Project-URL: Documentation, https://csaps.readthedocs.io
Project-URL: Code, https://github.com/espdev/csaps
Project-URL: Issue tracker, https://github.com/espdev/csaps/issues
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Scientific/Engineering :: Mathematics
Classifier: Topic :: Software Development :: Libraries
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: Implementation :: CPython
Requires-Python: >=3.6, <4
Description-Content-Type: text/markdown
Provides-Extra: docs
Provides-Extra: tests
License-File: LICENSE

**csaps** is a Python package for univariate, multivariate and n-dimensional grid data approximation using cubic smoothing splines.
The package can be useful in practical engineering tasks for data approximation and smoothing.
## Installing
Use pip for installing:
```
pip install -U csaps
```
The module depends only on NumPy and SciPy. Python 3.6 or above is supported.
## Simple Examples
Here is a couple of examples of smoothing data.
An univariate data smoothing:
```python
import numpy as np
import matplotlib.pyplot as plt
from csaps import csaps
np.random.seed(1234)
x = np.linspace(-5., 5., 25)
y = np.exp(-(x/2.5)**2) + (np.random.rand(25) - 0.2) * 0.3
xs = np.linspace(x[0], x[-1], 150)
ys = csaps(x, y, xs, smooth=0.85)
plt.plot(x, y, 'o', xs, ys, '-')
plt.show()
```
A surface data smoothing:
```python
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from csaps import csaps
np.random.seed(1234)
xdata = [np.linspace(-3, 3, 41), np.linspace(-3.5, 3.5, 31)]
i, j = np.meshgrid(*xdata, indexing='ij')
ydata = (3 * (1 - j)**2. * np.exp(-(j**2) - (i + 1)**2)
- 10 * (j / 5 - j**3 - i**5) * np.exp(-j**2 - i**2)
- 1 / 3 * np.exp(-(j + 1)**2 - i**2))
ydata = ydata + (np.random.randn(*ydata.shape) * 0.75)
ydata_s = csaps(xdata, ydata, xdata, smooth=0.988)
fig = plt.figure(figsize=(7, 4.5))
ax = fig.add_subplot(111, projection='3d')
ax.set_facecolor('none')
c = [s['color'] for s in plt.rcParams['axes.prop_cycle']]
ax.plot_wireframe(j, i, ydata, linewidths=0.5, color=c[0], alpha=0.5)
ax.scatter(j, i, ydata, s=10, c=c[0], alpha=0.5)
ax.plot_surface(j, i, ydata_s, color=c[1], linewidth=0, alpha=1.0)
ax.view_init(elev=9., azim=290)
plt.show()
```
## Documentation
More examples of usage and the full documentation can be found at https://csaps.readthedocs.io.
## Testing
We use pytest for testing.
```
cd /path/to/csaps/project/directory
pip install -e .[tests]
pytest
```
## Algorithm and Implementation
**csaps** Python package is inspired by MATLAB [CSAPS](https://www.mathworks.com/help/curvefit/csaps.html) function that is an implementation of
Fortran routine SMOOTH from [PGS](http://pages.cs.wisc.edu/~deboor/pgs/) (originally written by Carl de Boor).
Also the algothithm implementation in other languages:
* [csaps-rs](https://github.com/espdev/csaps-rs) Rust ndarray/sprs based implementation
* [csaps-cpp](https://github.com/espdev/csaps-cpp) C++11 Eigen based implementation (incomplete)
## References
C. de Boor, A Practical Guide to Splines, Springer-Verlag, 1978.
## License
[MIT](https://choosealicense.com/licenses/mit/)
# Changelog
## v1.1.0
* Introduced optional `normalizedsmooth` argument to reduce dependence on xdata and weights [#47](https://github.com/espdev/csaps/pull/47)
* Update numpy and scipy dependency ranges
## v1.0.4 (04.05.2021)
* Bump numpy dependency version
## v1.0.3 (01.01.2021)
* Bump scipy dependency version
* Bump sphinx dependency version and use m2r2 sphinx extension instead of m2r
* Add Python 3.9 to classifiers list and to Travis CI
* Set development status classifier to "5 - Production/Stable"
* Happy New Year!
## v1.0.2 (19.07.2020)
* Fix using 'nu' argument when n-d grid spline evaluating [#32](https://github.com/espdev/csaps/pull/32)
## v1.0.1 (19.07.2020)
* Fix n-d grid spline evaluating performance regression [#31](https://github.com/espdev/csaps/pull/31)
## v1.0.0 (11.07.2020)
* Use `PPoly` and `NdPPoly` base classes from SciPy interpolate module for `SplinePPForm` and `NdGridSplinePPForm` respectively.
* Remove deprecated classes `UnivariateCubicSmoothingSpline` and `MultivariateCubicSmoothingSpline`
* Update the documentation
**Notes**
In this release the spline representation (the array of spline coefficients) has been changed
according to `PPoly`/`NdPPoly`.
See SciPy [PPoly](https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.PPoly.html)
and [NdPPoly](https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.NdPPoly.html) documentation for details.
## v0.11.0 (28.03.2020)
* Internal re-design `SplinePPForm` and `NdGridSplinePPForm` classes [#17](https://github.com/espdev/csaps/issues/17):
- Remove `shape` and `axis` properties and reshaping data in these classes
- `NdGridSplinePPForm` coefficients array for 1D grid now is 1-d instead of 2-d
* Refactoring the code and decrease memory consumption
* Add `overload` type-hints for `csaps` function signatures
## v0.10.1 (19.03.2020)
* Fix call of `numpy.pad` function for numpy <1.17 [#15](https://github.com/espdev/csaps/issues/15)
## v0.10.0 (18.02.2020)
* Significant performance improvements for make/evaluate splines and memory consumption optimization
* Change format for storing spline coefficients (reshape coeffs array) to improve performance
* Add shape property to `SplinePPForm`/`NdGridSplinePPForm` and axis property to `SplinePPForm`
* Fix issues with the smoothing factor in nd-grid case: inverted ordering and unnable to use 0.0 value
* Update documentation
## v0.9.0 (21.01.2020)
* Drop support of Python 3.5
* `weights`, `smooth` and `axis` arguments in `csaps` function are keyword-only now
* `UnivariateCubicSmoothingSpline` and `MultivariateCubicSmoothingSpline` classes are deprecated
and will be removed in 1.0.0 version. Use `CubicSmoothingSpline` instead.
## v0.8.0 (13.01.2020)
* Add `csaps` function that can be used as the main API
* Refactor the internal structure of the package
* Add the [documentation](https://csaps.readthedocs.io)
**Attention**
This is the last version that supports Python 3.5.
The next versions will support Python 3.6 or above.
## v0.7.0 (19.09.2019)
* Add Generic-based type-hints and mypy-compatibility
## v0.6.1 (13.09.2019)
* A slight refactoring and extra data copies removing
## v0.6.0 (12.09.2019)
* Add "axis" parameter for univariate/multivariate cases
## v0.5.0 (10.06.2019)
* Reorganize the project to package-based structure
* Add the interface class for all smoothing spline classes
## v0.4.2 (07.09.2019)
* FIX: "smooth" value is 0.0 was not used
## v0.4.1 (30.05.2019)
* First PyPI release
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1623967905.0
csaps-1.1.0/README.md 0000666 0000000 0000000 00000010164 00000000000 012236 0 ustar 00 0000000 0000000

**csaps** is a Python package for univariate, multivariate and n-dimensional grid data approximation using cubic smoothing splines.
The package can be useful in practical engineering tasks for data approximation and smoothing.
## Installing
Use pip for installing:
```
pip install -U csaps
```
The module depends only on NumPy and SciPy. Python 3.6 or above is supported.
## Simple Examples
Here is a couple of examples of smoothing data.
An univariate data smoothing:
```python
import numpy as np
import matplotlib.pyplot as plt
from csaps import csaps
np.random.seed(1234)
x = np.linspace(-5., 5., 25)
y = np.exp(-(x/2.5)**2) + (np.random.rand(25) - 0.2) * 0.3
xs = np.linspace(x[0], x[-1], 150)
ys = csaps(x, y, xs, smooth=0.85)
plt.plot(x, y, 'o', xs, ys, '-')
plt.show()
```
A surface data smoothing:
```python
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from csaps import csaps
np.random.seed(1234)
xdata = [np.linspace(-3, 3, 41), np.linspace(-3.5, 3.5, 31)]
i, j = np.meshgrid(*xdata, indexing='ij')
ydata = (3 * (1 - j)**2. * np.exp(-(j**2) - (i + 1)**2)
- 10 * (j / 5 - j**3 - i**5) * np.exp(-j**2 - i**2)
- 1 / 3 * np.exp(-(j + 1)**2 - i**2))
ydata = ydata + (np.random.randn(*ydata.shape) * 0.75)
ydata_s = csaps(xdata, ydata, xdata, smooth=0.988)
fig = plt.figure(figsize=(7, 4.5))
ax = fig.add_subplot(111, projection='3d')
ax.set_facecolor('none')
c = [s['color'] for s in plt.rcParams['axes.prop_cycle']]
ax.plot_wireframe(j, i, ydata, linewidths=0.5, color=c[0], alpha=0.5)
ax.scatter(j, i, ydata, s=10, c=c[0], alpha=0.5)
ax.plot_surface(j, i, ydata_s, color=c[1], linewidth=0, alpha=1.0)
ax.view_init(elev=9., azim=290)
plt.show()
```
## Documentation
More examples of usage and the full documentation can be found at https://csaps.readthedocs.io.
## Testing
We use pytest for testing.
```
cd /path/to/csaps/project/directory
pip install -e .[tests]
pytest
```
## Algorithm and Implementation
**csaps** Python package is inspired by MATLAB [CSAPS](https://www.mathworks.com/help/curvefit/csaps.html) function that is an implementation of
Fortran routine SMOOTH from [PGS](http://pages.cs.wisc.edu/~deboor/pgs/) (originally written by Carl de Boor).
Also the algothithm implementation in other languages:
* [csaps-rs](https://github.com/espdev/csaps-rs) Rust ndarray/sprs based implementation
* [csaps-cpp](https://github.com/espdev/csaps-cpp) C++11 Eigen based implementation (incomplete)
## References
C. de Boor, A Practical Guide to Splines, Springer-Verlag, 1978.
## License
[MIT](https://choosealicense.com/licenses/mit/)
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1633430830.6784062
csaps-1.1.0/csaps/ 0000777 0000000 0000000 00000000000 00000000000 012066 5 ustar 00 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1623967905.0
csaps-1.1.0/csaps/__init__.py 0000666 0000000 0000000 00000001563 00000000000 014204 0 ustar 00 0000000 0000000 # -*- coding: utf-8 -*-
"""
Cubic spline approximation (smoothing)
"""
from csaps._version import __version__ # noqa
from csaps._base import (
ISplinePPForm,
ISmoothingSpline,
)
from csaps._sspumv import (
SplinePPForm,
CubicSmoothingSpline,
)
from csaps._sspndg import (
NdGridSplinePPForm,
NdGridCubicSmoothingSpline,
)
from csaps._types import (
UnivariateDataType,
MultivariateDataType,
NdGridDataType,
)
from csaps._shortcut import csaps, AutoSmoothingResult
__all__ = [
# Shortcut
'csaps',
'AutoSmoothingResult',
# Classes
'ISplinePPForm',
'ISmoothingSpline',
'SplinePPForm',
'NdGridSplinePPForm',
'CubicSmoothingSpline',
'NdGridCubicSmoothingSpline',
# Type-hints
'UnivariateDataType',
'MultivariateDataType',
'NdGridDataType',
]
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1623967905.0
csaps-1.1.0/csaps/_base.py 0000666 0000000 0000000 00000004764 00000000000 013524 0 ustar 00 0000000 0000000 # -*- coding: utf-8 -*-
"""
The base classes and interfaces
"""
import abc
from typing import Generic, Tuple, Optional
import numpy as np
from ._types import TData, TProps, TSmooth, TXi, TNu, TExtrapolate, TSpline
class ISplinePPForm(abc.ABC, Generic[TData, TProps]):
"""The interface class for spline representation in PP-form
"""
__module__ = 'csaps'
@property
@abc.abstractmethod
def breaks(self) -> TData:
"""Returns the breaks for the spline
Returns
-------
breaks : Union[np.ndarray, ty.Tuple[np.ndarray, ...]]
Breaks data
"""
@property
@abc.abstractmethod
def coeffs(self) -> np.ndarray:
"""Returns the spline coefficients
Returns
-------
coeffs : np.ndarray
Coefficients n-d array
"""
@property
@abc.abstractmethod
def order(self) -> TProps:
"""Returns the spline order
Returns
-------
order : ty.Union[int, ty.Tuple[int, ...]]
The spline order
"""
@property
@abc.abstractmethod
def pieces(self) -> TProps:
"""Returns the spline pieces data
Returns
-------
pieces : ty.Union[int, ty.Tuple[int, ...]]
The spline pieces data
"""
@property
@abc.abstractmethod
def ndim(self) -> int:
"""Returns the spline dimension count
Returns
-------
ndim : int
The spline dimension count
"""
@property
@abc.abstractmethod
def shape(self) -> Tuple[int]:
"""Returns the source data shape
Returns
-------
shape : tuple of int
The source data shape
"""
class ISmoothingSpline(abc.ABC, Generic[TSpline, TSmooth, TXi, TNu, TExtrapolate]):
"""The interface class for smooting splines
"""
__module__ = 'csaps'
@property
@abc.abstractmethod
def smooth(self) -> TSmooth:
"""Returns smoothing factor(s)
"""
@property
@abc.abstractmethod
def spline(self) -> TSpline:
"""Returns spline representation in PP-form
"""
@abc.abstractmethod
def __call__(self,
xi: TXi,
nu: Optional[TNu] = None,
extrapolate: Optional[TExtrapolate] = None) -> np.ndarray:
"""Evaluates spline on the data sites
"""
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1623967905.0
csaps-1.1.0/csaps/_reshape.py 0000666 0000000 0000000 00000012726 00000000000 014236 0 ustar 00 0000000 0000000 # -*- coding: utf-8 -*-
import functools
import operator
from itertools import chain
import typing as ty
import numpy as np
from numpy.lib.stride_tricks import as_strided
def prod(x):
"""Product of a list/tuple of numbers; ~40x faster vs np.prod for Python tuples"""
if len(x) == 0:
return 1
return functools.reduce(operator.mul, x)
def to_2d(arr: np.ndarray, axis: int) -> np.ndarray:
"""Transforms the shape of N-D array to 2-D NxM array
The function transforms N-D array to 2-D NxM array along given axis,
where N is dimension and M is the nember of elements.
The function does not create a copy.
Parameters
----------
arr : np.array
N-D array
axis : int
Axis that will be used for transform array shape
Returns
-------
arr2d : np.ndarray
2-D NxM array view
Raises
------
ValueError : axis is out of array axes
See Also
--------
from_2d
Examples
--------
.. code-block:: python
>>> shape = (2, 3, 4)
>>> arr = np.arange(1, np.prod(shape)+1).reshape(shape)
>>> arr_2d = to_2d(arr, axis=1)
>>> print(arr)
[[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]]
[[13 14 15 16]
[17 18 19 20]
[21 22 23 24]]]
>>> print(arr_2d)
[[ 1 5 9]
[ 2 6 10]
[ 3 7 11]
[ 4 8 12]
[13 17 21]
[14 18 22]
[15 19 23]
[16 20 24]]
"""
arr = np.asarray(arr)
axis = arr.ndim + axis if axis < 0 else axis
if axis >= arr.ndim: # pragma: no cover
raise ValueError(f'axis {axis} is out of array axes {arr.ndim}')
tr_axes = list(range(arr.ndim))
tr_axes.pop(axis)
tr_axes.append(axis)
new_shape = (np.prod(arr.shape) // arr.shape[axis], arr.shape[axis])
return arr.transpose(tr_axes).reshape(new_shape)
def umv_coeffs_to_canonical(arr: np.ndarray, pieces: int):
"""
Parameters
----------
arr : array
The 2-d array with shape (n, m) where:
n -- the number of spline dimensions (1 for univariate)
m -- order * pieces
pieces : int
The number of pieces
Returns
-------
arr_view : array view
The 2-d or 3-d array view with shape (k, p) or (k, p, n) where:
k -- spline order
p -- the number of spline pieces
n -- the number of spline dimensions (multivariate case)
"""
ndim = arr.shape[0]
order = arr.shape[1] // pieces
if ndim == 1:
shape = (order, pieces)
strides = (arr.strides[1] * pieces, arr.strides[1])
else:
shape = (order, pieces, ndim)
strides = (arr.strides[1] * pieces, arr.strides[1], arr.strides[0])
return as_strided(arr, shape=shape, strides=strides)
def umv_coeffs_to_flatten(arr: np.ndarray):
"""
Parameters
----------
arr : array
The 2-d or 3-d array with shape (k, m) or (k, m, n) where:
k -- the spline order
m -- the number of spline pieces
n -- the number of spline dimensions (multivariate case)
Returns
-------
arr_view : array view
The array 2-d view with shape (1, k * m) or (n, k * m)
"""
if arr.ndim == 2:
arr_view = arr.ravel()[np.newaxis]
elif arr.ndim == 3:
shape = (arr.shape[2], prod(arr.shape[:2]))
strides = arr.strides[:-3:-1]
arr_view = as_strided(arr, shape=shape, strides=strides)
else: # pragma: no cover
raise ValueError(
f"The array ndim must be 2 or 3, but given array has ndim={arr.ndim}.")
return arr_view
def ndg_coeffs_to_canonical(arr: np.ndarray, pieces: ty.Tuple[int]) -> np.ndarray:
"""Returns array canonical view for given n-d grid coeffs flatten array
Creates n-d array canonical view with shape (k0, ..., kn, p0, ..., pn) for given
array with shape (m0, ..., mn) and pieces (p0, ..., pn).
Parameters
----------
arr : array
The input array with shape (m0, ..., mn)
pieces : tuple
The number of pieces (p0, ..., pn)
Returns
-------
arr_view : array view
The canonical view for given array with shape (k0, ..., kn, p0, ..., pn)
"""
if arr.ndim > len(pieces):
return arr
shape = tuple(sz // p for sz, p in zip(arr.shape, pieces)) + pieces
strides = tuple(st * p for st, p in zip(arr.strides, pieces)) + arr.strides
return as_strided(arr, shape=shape, strides=strides)
def ndg_coeffs_to_flatten(arr: np.ndarray):
"""Creates flatten array view for n-d grid coeffs canonical array
For example for input array (4, 4, 20, 30) will be created the flatten view (80, 120)
Parameters
----------
arr : array
The input array with shape (k0, ..., kn, p0, ..., pn) where:
``k0, ..., kn`` -- spline orders
``p0, ..., pn`` -- spline pieces
Returns
-------
arr_view : array view
Flatten view of array with shape (m0, ..., mn)
"""
if arr.ndim == 2:
return arr
ndim = arr.ndim // 2
axes = tuple(chain.from_iterable(zip(range(ndim), range(ndim, arr.ndim))))
shape = tuple(prod(arr.shape[i::ndim]) for i in range(ndim))
return arr.transpose(axes).reshape(shape)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633430697.0
csaps-1.1.0/csaps/_shortcut.py 0000666 0000000 0000000 00000017552 00000000000 014464 0 ustar 00 0000000 0000000 # -*- coding: utf-8 -*-
"""
The module provised `csaps` shortcut function for smoothing data
"""
from collections import abc as c_abc
from typing import Optional, Union, Sequence, NamedTuple, overload
from ._base import ISmoothingSpline
from ._sspumv import CubicSmoothingSpline
from ._sspndg import ndgrid_prepare_data_vectors, NdGridCubicSmoothingSpline
from ._types import UnivariateDataType, MultivariateDataType, NdGridDataType
class AutoSmoothingResult(NamedTuple):
"""The result for auto smoothing for `csaps` function"""
values: MultivariateDataType
"""Smoothed data values"""
smooth: Union[float, Sequence[Optional[float]]]
"""The calculated smoothing parameter"""
# **************************************
# csaps signatures
#
@overload
def csaps(xdata: UnivariateDataType,
ydata: MultivariateDataType,
*,
weights: Optional[UnivariateDataType] = None,
smooth: Optional[float] = None,
axis: Optional[int] = None,
normalizedsmooth: bool = False) -> ISmoothingSpline: ...
@overload
def csaps(xdata: UnivariateDataType,
ydata: MultivariateDataType,
xidata: UnivariateDataType,
*,
weights: Optional[UnivariateDataType] = None,
axis: Optional[int] = None,
normalizedsmooth: bool = False) -> AutoSmoothingResult: ...
@overload
def csaps(xdata: UnivariateDataType,
ydata: MultivariateDataType,
xidata: UnivariateDataType,
*,
smooth: float,
weights: Optional[UnivariateDataType] = None,
axis: Optional[int] = None,
normalizedsmooth: bool = False) -> MultivariateDataType: ...
@overload
def csaps(xdata: NdGridDataType,
ydata: MultivariateDataType,
*,
weights: Optional[NdGridDataType] = None,
smooth: Optional[Sequence[float]] = None,
axis: Optional[int] = None,
normalizedsmooth: bool = False) -> ISmoothingSpline: ...
@overload
def csaps(xdata: NdGridDataType,
ydata: MultivariateDataType,
xidata: NdGridDataType,
*,
weights: Optional[NdGridDataType] = None,
axis: Optional[int] = None,
normalizedsmooth: bool = False) -> AutoSmoothingResult: ...
@overload
def csaps(xdata: NdGridDataType,
ydata: MultivariateDataType,
xidata: NdGridDataType,
*,
smooth: Sequence[float],
weights: Optional[NdGridDataType] = None,
axis: Optional[int] = None,
normalizedsmooth: bool = False) -> MultivariateDataType: ...
#
# csaps signatures
# **************************************
def csaps(xdata: Union[UnivariateDataType, NdGridDataType],
ydata: MultivariateDataType,
xidata: Optional[Union[UnivariateDataType, NdGridDataType]] = None,
*,
weights: Optional[Union[UnivariateDataType, NdGridDataType]] = None,
smooth: Optional[Union[float, Sequence[float]]] = None,
axis: Optional[int] = None,
normalizedsmooth: bool = False) -> Union[MultivariateDataType, ISmoothingSpline, AutoSmoothingResult]:
"""Smooths the univariate/multivariate/gridded data or computes the corresponding splines
This function might be used as the main API for smoothing any data.
Parameters
----------
xdata : np.ndarray, array-like
The data sites ``x1 < x2 < ... < xN``:
- 1-D data vector/sequence (array-like) for univariate/multivariate ``ydata`` case
- The sequence of 1-D data vectors for nd-gridded ``ydata`` case
ydata : np.ndarray, array-like
The data values:
- 1-D data vector/sequence (array-like) for univariate data case
- N-D array/array-like for multivariate data case
- N-D array for nd-gridded data case
xidata : [*Optional*] np.ndarray, array-like, Sequence[array-like]
The data sites for output smoothed data:
- 1-D data vector/sequence (array-like) for univariate/multivariate ``ydata`` case
- The sequence of 1-D data vectors for nd-gridded ``ydata`` case
If this argument was not set, the function will return computed spline
for given data in :class:`ISmoothingSpline` object.
weights : [*Optional*] np.ndarray, array-like, Sequence[array-like]
The weights data vectors:
- 1-D data vector/sequence (array-like) for univariate/multivariate ``ydata`` case
- The sequence of 1-D data vectors for nd-gridded ``ydata`` case
smooth : [*Optional*] float, Sequence[float]
The smoothing factor value(s):
- float value in the range ``[0, 1]`` for univariate/multivariate ``ydata`` case
- the sequence of float in the range ``[0, 1]`` or None for nd-gridded ``ydata`` case
If this argument was not set or None or sequence with None-items, the function will return
named tuple :class:`AutoSmoothingResult` with computed smoothed data values and smoothing factor value(s).
axis : [*Optional*] int
The ``ydata`` axis. Axis along which ``ydata`` is assumed to be varying.
If this argument was not set the last axis will be used (``axis == -1``).
.. note::
Currently, `axis` will be ignored for nd-gridded ``ydata`` case.
normalizedsmooth : [*Optional*] bool
If True, the smooth parameter is normalized such that results are invariant to xdata range
and less sensitive to nonuniformity of weights and xdata clumping
.. versionadded:: 1.1.0
Returns
-------
yidata : np.ndarray
Smoothed data values if ``xidata`` and ``smooth`` were set.
autosmoothing_result : AutoSmoothingResult
The named tuple object with two fileds:
- 'values' -- smoothed data values
- 'smooth' -- computed smoothing factor
This result will be returned if ``xidata`` was set and ``smooth`` was not set.
ssp_obj : ISmoothingSpline
Smoothing spline object if ``xidata`` was not set:
- :class:`CubicSmoothingSpline` instance for univariate/multivariate data
- :class:`NdGridCubicSmoothingSpline` instance for nd-gridded data
Examples
--------
Univariate data smoothing
.. code-block:: python
import numpy as np
from csaps import csaps
x = np.linspace(-5., 5., 25)
y = np.exp(-(x/2.5)**2) + (np.random.rand(25) - 0.2) * 0.3
xi = np.linspace(-5., 5., 150)
# Smooth data with smoothing factor 0.85
yi = csaps(x, y, xi, smooth=0.85)
# Smooth data and compute smoothing factor automatically
yi, smooth = csaps(x, y, xi)
# Do not evaluate the spline, only compute it
sp = csaps(x, y, smooth=0.98)
See Also
--------
CubicSmoothingSpline
NdGridCubicSmoothingSpline
"""
if isinstance(xdata, c_abc.Sequence):
try:
ndgrid_prepare_data_vectors(xdata, 'xdata')
except ValueError:
umv = True
else:
umv = False
else:
umv = True
if umv:
axis = -1 if axis is None else axis
sp = CubicSmoothingSpline(xdata, ydata, weights=weights, smooth=smooth, axis=axis,
normalizedsmooth=normalizedsmooth)
else:
sp = NdGridCubicSmoothingSpline(xdata, ydata, weights, smooth, normalizedsmooth=normalizedsmooth)
if xidata is None:
return sp
yidata = sp(xidata)
auto_smooth = smooth is None
if isinstance(smooth, Sequence):
auto_smooth = any(sm is None for sm in smooth)
if auto_smooth:
return AutoSmoothingResult(yidata, sp.smooth)
else:
return yidata
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633430697.0
csaps-1.1.0/csaps/_sspndg.py 0000666 0000000 0000000 00000026662 00000000000 014111 0 ustar 00 0000000 0000000 # -*- coding: utf-8 -*-
"""
ND-Gridded cubic smoothing spline implementation
"""
import collections.abc as c_abc
from numbers import Number
from typing import Tuple, Sequence, Optional, Union
import numpy as np
from scipy.interpolate import PPoly, NdPPoly
from ._base import ISplinePPForm, ISmoothingSpline
from ._types import UnivariateDataType, NdGridDataType
from ._sspumv import CubicSmoothingSpline
from ._reshape import (
prod,
umv_coeffs_to_canonical,
umv_coeffs_to_flatten,
ndg_coeffs_to_canonical,
ndg_coeffs_to_flatten,
)
def ndgrid_prepare_data_vectors(data, name, min_size: int = 2) -> Tuple[np.ndarray, ...]:
if not isinstance(data, c_abc.Sequence):
raise TypeError(f"'{name}' must be a sequence of 1-d array-like (vectors) or scalars.")
data = list(data)
for axis, d in enumerate(data):
d = np.asarray(d, dtype=np.float64)
if d.ndim > 1:
raise ValueError(f"All '{name}' elements must be a vector for axis {axis}.")
if d.size < min_size:
raise ValueError(f"'{name}' must contain at least {min_size} data points for axis {axis}.")
data[axis] = d
return tuple(data)
class NdGridSplinePPForm(ISplinePPForm[Tuple[np.ndarray, ...], Tuple[int, ...]],
NdPPoly):
"""N-D grid spline representation in PP-form
N-D grid spline is represented in piecewise tensor product polynomial form.
Notes
-----
Inherited from :py:class:`scipy.interpolate.NdPPoly`
"""
__module__ = 'csaps'
@property
def breaks(self) -> Tuple[np.ndarray, ...]:
return self.x
@property
def coeffs(self) -> np.ndarray:
return self.c
@property
def order(self) -> Tuple[int, ...]:
return self.c.shape[:self.c.ndim // 2]
@property
def pieces(self) -> Tuple[int, ...]:
return self.c.shape[self.c.ndim // 2:]
@property
def ndim(self) -> int:
return len(self.x)
@property
def shape(self) -> Tuple[int, ...]:
return tuple(len(xi) for xi in self.x)
def __call__(self,
x: Sequence[UnivariateDataType],
nu: Optional[Tuple[int, ...]] = None,
extrapolate: Optional[bool] = None) -> np.ndarray:
"""Evaluate the spline for given data
Parameters
----------
x : tuple of 1-d array-like
The tuple of point values for each dimension to evaluate the spline at.
nu : [*Optional*] tuple of int
Orders of derivatives to evaluate. Each must be non-negative.
extrapolate : [*Optional*] bool
Whether to extrapolate to out-of-bounds points based on first and last
intervals, or to return NaNs.
Returns
-------
y : array-like
Interpolated values. Shape is determined by replacing the
interpolation axis in the original array with the shape of x.
"""
x = ndgrid_prepare_data_vectors(x, 'x', min_size=1)
if len(x) != self.ndim:
raise ValueError(
f"'x' sequence must have length {self.ndim} according to 'breaks'")
if nu is None:
nu = (0,) * len(x)
if extrapolate is None:
extrapolate = True
shape = tuple(x.size for x in x)
coeffs = ndg_coeffs_to_flatten(self.coeffs)
coeffs_shape = coeffs.shape
ndim_m1 = self.ndim - 1
permuted_axes = (ndim_m1, *range(ndim_m1))
for i in reversed(range(self.ndim)):
umv_ndim = prod(coeffs_shape[:ndim_m1])
c_shape = (umv_ndim, self.pieces[i] * self.order[i])
if c_shape != coeffs_shape:
coeffs = coeffs.reshape(c_shape)
coeffs_cnl = umv_coeffs_to_canonical(coeffs, self.pieces[i])
spline = PPoly.construct_fast(coeffs_cnl, self.breaks[i], axis=1)
coeffs = spline(x[i], nu=nu[i], extrapolate=extrapolate)
shape_r = (*coeffs_shape[:ndim_m1], shape[i])
coeffs = coeffs.reshape(shape_r).transpose(permuted_axes)
coeffs_shape = coeffs.shape
return coeffs.reshape(shape)
def __repr__(self): # pragma: no cover
return (
f'{type(self).__name__}\n'
f' breaks: {self.breaks}\n'
f' coeffs shape: {self.coeffs.shape}\n'
f' data shape: {self.shape}\n'
f' pieces: {self.pieces}\n'
f' order: {self.order}\n'
f' ndim: {self.ndim}\n'
)
class NdGridCubicSmoothingSpline(ISmoothingSpline[
NdGridSplinePPForm,
Tuple[float, ...],
NdGridDataType,
Tuple[int, ...],
bool,
]):
"""N-D grid cubic smoothing spline
Class implements N-D grid data smoothing (piecewise tensor product polynomial).
Parameters
----------
xdata : list, tuple, Sequence[vector-like]
X data site vectors for each dimensions. These vectors determine ND-grid.
For example::
# 2D grid
x = [
np.linspace(0, 5, 21),
np.linspace(0, 6, 25),
]
ydata : np.ndarray
Y data ND-array with shape equal ``xdata`` vector sizes
weights : [*Optional*] list, tuple, Sequence[vector-like]
Weights data vector(s) for all dimensions or each dimension with
size(s) equal to ``xdata`` sizes
smooth : [*Optional*] float, Sequence[float]
The smoothing parameter (or a sequence of parameters for each dimension) in range ``[0, 1]`` where:
- 0: The smoothing spline is the least-squares straight line fit
- 1: The cubic spline interpolant with natural condition
normalizedsmooth : [*Optional*] bool
If True, the smooth parameter is normalized such that results are invariant to xdata range
and less sensitive to nonuniformity of weights and xdata clumping
.. versionadded:: 1.1.0
"""
__module__ = 'csaps'
def __init__(self,
xdata: NdGridDataType,
ydata: np.ndarray,
weights: Optional[Union[UnivariateDataType, NdGridDataType]] = None,
smooth: Optional[Union[float, Sequence[Optional[float]]]] = None,
normalizedsmooth: bool = False) -> None:
x, y, w, s = self._prepare_data(xdata, ydata, weights, smooth)
coeffs, smooth = self._make_spline(x, y, w, s, normalizedsmooth)
self._spline = NdGridSplinePPForm.construct_fast(coeffs, x)
self._smooth = smooth
def __call__(self,
x: Union[NdGridDataType, Sequence[Number]],
nu: Optional[Tuple[int, ...]] = None,
extrapolate: Optional[bool] = None) -> np.ndarray:
"""Evaluate the spline for given data
Parameters
----------
x : tuple of 1-d array-like
The tuple of point values for each dimension to evaluate the spline at.
nu : [*Optional*] tuple of int
Orders of derivatives to evaluate. Each must be non-negative.
extrapolate : [*Optional*] bool
Whether to extrapolate to out-of-bounds points based on first and last
intervals, or to return NaNs.
Returns
-------
y : array-like
Interpolated values. Shape is determined by replacing the
interpolation axis in the original array with the shape of x.
"""
return self._spline(x, nu=nu, extrapolate=extrapolate)
@property
def smooth(self) -> Tuple[float, ...]:
"""Returns a tuple of smoothing parameters for each axis
Returns
-------
smooth : Tuple[float, ...]
The smoothing parameter in the range ``[0, 1]`` for each axis
"""
return self._smooth
@property
def spline(self) -> NdGridSplinePPForm:
"""Returns the spline description in 'NdGridSplinePPForm' instance
Returns
-------
spline : NdGridSplinePPForm
The spline description in :class:`NdGridSplinePPForm` instance
"""
return self._spline
@classmethod
def _prepare_data(cls, xdata, ydata, weights, smooth):
xdata = ndgrid_prepare_data_vectors(xdata, 'xdata')
ydata = np.asarray(ydata)
data_ndim = len(xdata)
if ydata.ndim != data_ndim:
raise ValueError(
f"'ydata' must have dimension {data_ndim} according to 'xdata'")
for axis, (yd, xs) in enumerate(zip(ydata.shape, map(len, xdata))):
if yd != xs:
raise ValueError(
f"'ydata' ({yd}) and xdata ({xs}) sizes mismatch for axis {axis}")
if not weights:
weights = [None] * data_ndim
else:
weights = ndgrid_prepare_data_vectors(weights, 'weights')
if len(weights) != data_ndim:
raise ValueError(
f"'weights' ({len(weights)}) and 'xdata' ({data_ndim}) dimensions mismatch")
for axis, (w, x) in enumerate(zip(weights, xdata)):
if w is not None:
if w.size != x.size:
raise ValueError(
f"'weights' ({w.size}) and 'xdata' ({x.size}) sizes mismatch for axis {axis}")
if smooth is None:
smooth = [None] * data_ndim
if not isinstance(smooth, c_abc.Sequence):
smooth = [float(smooth)] * data_ndim
else:
smooth = list(smooth)
if len(smooth) != data_ndim:
raise ValueError(
'Number of smoothing parameter values must '
f'be equal number of dimensions ({data_ndim})')
return xdata, ydata, weights, smooth
@staticmethod
def _make_spline(xdata, ydata, weights, smooth, normalizedsmooth):
ndim = len(xdata)
if ndim == 1:
s = CubicSmoothingSpline(
xdata[0], ydata, weights=weights[0], smooth=smooth[0], normalizedsmooth=normalizedsmooth)
return s.spline.coeffs, (s.smooth,)
shape = ydata.shape
coeffs = ydata
coeffs_shape = list(shape)
smooths = []
permute_axes = (ndim - 1, *range(ndim - 1))
# computing coordinatewise smoothing spline
for i in reversed(range(ndim)):
if ndim > 2:
coeffs = coeffs.reshape(prod(coeffs.shape[:-1]), coeffs.shape[-1])
s = CubicSmoothingSpline(
xdata[i], coeffs, weights=weights[i], smooth=smooth[i], normalizedsmooth=normalizedsmooth)
smooths.append(s.smooth)
coeffs = umv_coeffs_to_flatten(s.spline.coeffs)
if ndim > 2:
coeffs_shape[-1] = s.spline.pieces * s.spline.order
coeffs = coeffs.reshape(coeffs_shape)
coeffs = coeffs.transpose(permute_axes)
coeffs_shape = list(coeffs.shape)
pieces = tuple(int(size - 1) for size in shape)
coeffs = ndg_coeffs_to_canonical(coeffs.squeeze(), pieces)
return coeffs, tuple(reversed(smooths))
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633430697.0
csaps-1.1.0/csaps/_sspumv.py 0000666 0000000 0000000 00000024774 00000000000 014152 0 ustar 00 0000000 0000000 # -*- coding: utf-8 -*-
"""
Univariate/multivariate cubic smoothing spline implementation
"""
import functools
from typing import Optional, Union, Tuple, List
import numpy as np
import scipy.sparse as sp
import scipy.sparse.linalg as la
from scipy.interpolate import PPoly
from ._base import ISplinePPForm, ISmoothingSpline
from ._types import UnivariateDataType, MultivariateDataType
from ._reshape import to_2d, prod
class SplinePPForm(ISplinePPForm[np.ndarray, int], PPoly):
"""The base class for univariate/multivariate spline in piecewise polynomial form
Piecewise polynomial in terms of coefficients and breakpoints.
Notes
-----
Inherited from :py:class:`scipy.interpolate.PPoly`
"""
__module__ = 'csaps'
@property
def breaks(self) -> np.ndarray:
return self.x
@property
def coeffs(self) -> np.ndarray:
return self.c
@property
def order(self) -> int:
return self.c.shape[0]
@property
def pieces(self) -> int:
return self.c.shape[1]
@property
def ndim(self) -> int:
"""Returns the number of spline dimensions
The number of dimensions is product of shape without ``shape[self.axis]``.
"""
shape = list(self.shape)
shape.pop(self.axis)
return prod(shape)
@property
def shape(self) -> Tuple[int]:
"""Returns the source data shape
"""
shape: List[int] = list(self.c.shape[2:])
shape.insert(self.axis, self.c.shape[1] + 1)
return tuple(shape)
def __repr__(self): # pragma: no cover
return (
f'{type(self).__name__}\n'
f' breaks: {self.breaks}\n'
f' coeffs shape: {self.coeffs.shape}\n'
f' data shape: {self.shape}\n'
f' axis: {self.axis}\n'
f' pieces: {self.pieces}\n'
f' order: {self.order}\n'
f' ndim: {self.ndim}\n'
)
class CubicSmoothingSpline(ISmoothingSpline[
SplinePPForm,
float,
UnivariateDataType,
int,
Union[bool, str]
]):
"""Cubic smoothing spline
The cubic spline implementation for univariate/multivariate data.
Parameters
----------
xdata : np.ndarray, sequence, vector-like
X input 1-D data vector (data sites: ``x1 < x2 < ... < xN``)
ydata : np.ndarray, vector-like, sequence[vector-like]
Y input 1-D data vector or ND-array with shape[axis] equal of `xdata` size)
weights : [*Optional*] np.ndarray, list
Weights 1-D vector with size equal of ``xdata`` size
smooth : [*Optional*] float
Smoothing parameter in range [0, 1] where:
- 0: The smoothing spline is the least-squares straight line fit
- 1: The cubic spline interpolant with natural condition
axis : [*Optional*] int
Axis along which ``ydata`` is assumed to be varying.
Meaning that for x[i] the corresponding values are np.take(ydata, i, axis=axis).
By default is -1 (the last axis).
normalizedsmooth : [*Optional*] bool
If True, the smooth parameter is normalized such that results are invariant to xdata range
and less sensitive to nonuniformity of weights and xdata clumping
.. versionadded:: 1.1.0
"""
__module__ = 'csaps'
def __init__(self,
xdata: UnivariateDataType,
ydata: MultivariateDataType,
weights: Optional[UnivariateDataType] = None,
smooth: Optional[float] = None,
axis: int = -1,
normalizedsmooth: bool = False):
x, y, w, shape, axis = self._prepare_data(xdata, ydata, weights, axis)
coeffs, smooth = self._make_spline(x, y, w, smooth, shape, normalizedsmooth)
spline = SplinePPForm.construct_fast(coeffs, x, axis=axis)
self._smooth = smooth
self._spline = spline
def __call__(self,
x: UnivariateDataType,
nu: Optional[int] = None,
extrapolate: Optional[Union[bool, str]] = None) -> np.ndarray:
"""Evaluate the spline for given data
Parameters
----------
x : 1-d array-like
Points to evaluate the spline at.
nu : [*Optional*] int
Order of derivative to evaluate. Must be non-negative.
extrapolate : [*Optional*] bool or 'periodic'
If bool, determines whether to extrapolate to out-of-bounds points
based on first and last intervals, or to return NaNs. If 'periodic',
periodic extrapolation is used. Default is True.
Notes
-----
Derivatives are evaluated piecewise for each polynomial
segment, even if the polynomial is not differentiable at the
breakpoints. The polynomial intervals are considered half-open,
``[a, b)``, except for the last interval which is closed
``[a, b]``.
"""
if nu is None:
nu = 0
return self._spline(x, nu=nu, extrapolate=extrapolate)
@property
def smooth(self) -> float:
"""Returns the smoothing factor
Returns
-------
smooth : float
Smoothing factor in the range [0, 1]
"""
return self._smooth
@property
def spline(self) -> SplinePPForm:
"""Returns the spline description in `SplinePPForm` instance
Returns
-------
spline : SplinePPForm
The spline representation in :class:`SplinePPForm` instance
"""
return self._spline
@staticmethod
def _prepare_data(xdata, ydata, weights, axis):
xdata = np.asarray(xdata, dtype=np.float64)
ydata = np.asarray(ydata, dtype=np.float64)
if xdata.ndim > 1:
raise ValueError("'xdata' must be a vector")
if xdata.size < 2:
raise ValueError("'xdata' must contain at least 2 data points.")
axis = ydata.ndim + axis if axis < 0 else axis
if ydata.shape[axis] != xdata.size:
raise ValueError(
f"'ydata' data must be a 1-D or N-D array with shape[{axis}] "
f"that is equal to 'xdata' size ({xdata.size})")
# Rolling axis for using its shape while constructing coeffs array
shape = np.rollaxis(ydata, axis).shape
# Reshape ydata N-D array to 2-D NxM array where N is the data
# dimension and M is the number of data points.
ydata = to_2d(ydata, axis)
if weights is None:
weights = np.ones_like(xdata)
else:
weights = np.asarray(weights, dtype=np.float64)
if weights.size != xdata.size:
raise ValueError('Weights vector size must be equal of xdata size')
return xdata, ydata, weights, shape, axis
@staticmethod
def _compute_smooth(a, b):
"""
The calculation of the smoothing spline requires the solution of a
linear system whose coefficient matrix has the form p*A + (1-p)*B, with
the matrices A and B depending on the data sites x. The default value
of p makes p*trace(A) equal (1 - p)*trace(B).
"""
def trace(m: sp.dia_matrix):
return m.diagonal().sum()
return 1. / (1. + trace(a) / (6. * trace(b)))
@staticmethod
def _normalize_smooth(x: np.ndarray, w: np.ndarray, smooth: Optional[float]):
"""
See the explanation here: https://github.com/espdev/csaps/pull/47
"""
span = np.ptp(x)
eff_x = 1 + (span ** 2) / np.sum(np.diff(x) ** 2)
eff_w = np.sum(w) ** 2 / np.sum(w ** 2)
k = 80 * (span ** 3) * (x.size ** -2) * (eff_x ** -0.5) * (eff_w ** -0.5)
s = 0.5 if smooth is None else smooth
p = s / (s + (1 - s) * k)
return p
@staticmethod
def _make_spline(x, y, w, smooth, shape, normalizedsmooth):
pcount = x.size
dx = np.diff(x)
if not all(dx > 0): # pragma: no cover
raise ValueError(
"Items of 'xdata' vector must satisfy the condition: x1 < x2 < ... < xN")
dy = np.diff(y, axis=1)
dy_dx = dy / dx
if pcount == 2:
# The corner case for the data with 2 points (1 breaks interval)
# In this case we have 2-ordered spline and linear interpolation in fact
yi = y[:, 0][:, np.newaxis]
c_shape = (2, pcount - 1) + shape[1:]
c = np.vstack((dy_dx, yi)).reshape(c_shape)
p = 1.0
return c, p
# Create diagonal sparse matrices
diags_r = np.vstack((dx[1:], 2 * (dx[1:] + dx[:-1]), dx[:-1]))
r = sp.spdiags(diags_r, [-1, 0, 1], pcount - 2, pcount - 2)
dx_recip = 1. / dx
diags_qtw = np.vstack((dx_recip[:-1], -(dx_recip[1:] + dx_recip[:-1]), dx_recip[1:]))
diags_sqrw_recip = 1. / np.sqrt(w)
qtw = (sp.diags(diags_qtw, [0, 1, 2], (pcount - 2, pcount)) @
sp.diags(diags_sqrw_recip, 0, (pcount, pcount)))
qtw = qtw @ qtw.T
p = smooth
if normalizedsmooth:
p = CubicSmoothingSpline._normalize_smooth(x, w, smooth)
elif smooth is None:
p = CubicSmoothingSpline._compute_smooth(r, qtw)
pp = (6. * (1. - p))
# Solve linear system for the 2nd derivatives
a = pp * qtw + p * r
b = np.diff(dy_dx, axis=1).T
u = la.spsolve(a, b)
if u.ndim < 2:
u = u[np.newaxis]
if y.shape[0] == 1:
u = u.T
dx = dx[:, np.newaxis]
vpad = functools.partial(np.pad, pad_width=[(1, 1), (0, 0)], mode='constant')
d1 = np.diff(vpad(u), axis=0) / dx
d2 = np.diff(vpad(d1), axis=0)
diags_w_recip = 1. / w
w = sp.diags(diags_w_recip, 0, (pcount, pcount))
yi = y.T - (pp * w) @ d2
pu = vpad(p * u)
c1 = np.diff(pu, axis=0) / dx
c2 = 3. * pu[:-1, :]
c3 = np.diff(yi, axis=0) / dx - dx * (2. * pu[:-1, :] + pu[1:, :])
c4 = yi[:-1, :]
c_shape = (4, pcount - 1) + shape[1:]
c = np.vstack((c1, c2, c3, c4)).reshape(c_shape)
return c, p
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1623967905.0
csaps-1.1.0/csaps/_types.py 0000666 0000000 0000000 00000001325 00000000000 013744 0 ustar 00 0000000 0000000 # -*- coding: utf-8 -*-
"""
Type-hints and type vars
"""
from collections import abc
from typing import Union, Sequence, Tuple, TypeVar
from numbers import Number
import numpy as np
UnivariateDataType = Union[np.ndarray, Sequence[Number]]
MultivariateDataType = Union[np.ndarray, abc.Sequence]
NdGridDataType = Sequence[UnivariateDataType]
TData = TypeVar('TData', np.ndarray, Sequence[np.ndarray])
TProps = TypeVar('TProps', int, Tuple[int, ...])
TSmooth = TypeVar('TSmooth', float, Tuple[float, ...])
TXi = TypeVar('TXi', UnivariateDataType, NdGridDataType)
TNu = TypeVar('TNu', int, Tuple[int, ...])
TExtrapolate = TypeVar('TExtrapolate', bool, Union[bool, str])
TSpline = TypeVar('TSpline')
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633430724.0
csaps-1.1.0/csaps/_version.py 0000666 0000000 0000000 00000000062 00000000000 014262 0 ustar 00 0000000 0000000 # -*- coding: utf-8 -*-
__version__ = '1.1.0'
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1623967905.0
csaps-1.1.0/csaps/py.typed 0000666 0000000 0000000 00000000000 00000000000 013553 0 ustar 00 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1633430830.6814058
csaps-1.1.0/csaps.egg-info/ 0000777 0000000 0000000 00000000000 00000000000 013560 5 ustar 00 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633430830.0
csaps-1.1.0/csaps.egg-info/PKG-INFO 0000666 0000000 0000000 00000022054 00000000000 014660 0 ustar 00 0000000 0000000 Metadata-Version: 2.1
Name: csaps
Version: 1.1.0
Summary: Cubic spline approximation (smoothing)
Home-page: https://github.com/espdev/csaps
Author: Eugene Prilepin
Author-email: esp.home@gmail.com
License: MIT
Project-URL: Documentation, https://csaps.readthedocs.io
Project-URL: Code, https://github.com/espdev/csaps
Project-URL: Issue tracker, https://github.com/espdev/csaps/issues
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Scientific/Engineering :: Mathematics
Classifier: Topic :: Software Development :: Libraries
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: Implementation :: CPython
Requires-Python: >=3.6, <4
Description-Content-Type: text/markdown
Provides-Extra: docs
Provides-Extra: tests
License-File: LICENSE

**csaps** is a Python package for univariate, multivariate and n-dimensional grid data approximation using cubic smoothing splines.
The package can be useful in practical engineering tasks for data approximation and smoothing.
## Installing
Use pip for installing:
```
pip install -U csaps
```
The module depends only on NumPy and SciPy. Python 3.6 or above is supported.
## Simple Examples
Here is a couple of examples of smoothing data.
An univariate data smoothing:
```python
import numpy as np
import matplotlib.pyplot as plt
from csaps import csaps
np.random.seed(1234)
x = np.linspace(-5., 5., 25)
y = np.exp(-(x/2.5)**2) + (np.random.rand(25) - 0.2) * 0.3
xs = np.linspace(x[0], x[-1], 150)
ys = csaps(x, y, xs, smooth=0.85)
plt.plot(x, y, 'o', xs, ys, '-')
plt.show()
```
A surface data smoothing:
```python
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from csaps import csaps
np.random.seed(1234)
xdata = [np.linspace(-3, 3, 41), np.linspace(-3.5, 3.5, 31)]
i, j = np.meshgrid(*xdata, indexing='ij')
ydata = (3 * (1 - j)**2. * np.exp(-(j**2) - (i + 1)**2)
- 10 * (j / 5 - j**3 - i**5) * np.exp(-j**2 - i**2)
- 1 / 3 * np.exp(-(j + 1)**2 - i**2))
ydata = ydata + (np.random.randn(*ydata.shape) * 0.75)
ydata_s = csaps(xdata, ydata, xdata, smooth=0.988)
fig = plt.figure(figsize=(7, 4.5))
ax = fig.add_subplot(111, projection='3d')
ax.set_facecolor('none')
c = [s['color'] for s in plt.rcParams['axes.prop_cycle']]
ax.plot_wireframe(j, i, ydata, linewidths=0.5, color=c[0], alpha=0.5)
ax.scatter(j, i, ydata, s=10, c=c[0], alpha=0.5)
ax.plot_surface(j, i, ydata_s, color=c[1], linewidth=0, alpha=1.0)
ax.view_init(elev=9., azim=290)
plt.show()
```
## Documentation
More examples of usage and the full documentation can be found at https://csaps.readthedocs.io.
## Testing
We use pytest for testing.
```
cd /path/to/csaps/project/directory
pip install -e .[tests]
pytest
```
## Algorithm and Implementation
**csaps** Python package is inspired by MATLAB [CSAPS](https://www.mathworks.com/help/curvefit/csaps.html) function that is an implementation of
Fortran routine SMOOTH from [PGS](http://pages.cs.wisc.edu/~deboor/pgs/) (originally written by Carl de Boor).
Also the algothithm implementation in other languages:
* [csaps-rs](https://github.com/espdev/csaps-rs) Rust ndarray/sprs based implementation
* [csaps-cpp](https://github.com/espdev/csaps-cpp) C++11 Eigen based implementation (incomplete)
## References
C. de Boor, A Practical Guide to Splines, Springer-Verlag, 1978.
## License
[MIT](https://choosealicense.com/licenses/mit/)
# Changelog
## v1.1.0
* Introduced optional `normalizedsmooth` argument to reduce dependence on xdata and weights [#47](https://github.com/espdev/csaps/pull/47)
* Update numpy and scipy dependency ranges
## v1.0.4 (04.05.2021)
* Bump numpy dependency version
## v1.0.3 (01.01.2021)
* Bump scipy dependency version
* Bump sphinx dependency version and use m2r2 sphinx extension instead of m2r
* Add Python 3.9 to classifiers list and to Travis CI
* Set development status classifier to "5 - Production/Stable"
* Happy New Year!
## v1.0.2 (19.07.2020)
* Fix using 'nu' argument when n-d grid spline evaluating [#32](https://github.com/espdev/csaps/pull/32)
## v1.0.1 (19.07.2020)
* Fix n-d grid spline evaluating performance regression [#31](https://github.com/espdev/csaps/pull/31)
## v1.0.0 (11.07.2020)
* Use `PPoly` and `NdPPoly` base classes from SciPy interpolate module for `SplinePPForm` and `NdGridSplinePPForm` respectively.
* Remove deprecated classes `UnivariateCubicSmoothingSpline` and `MultivariateCubicSmoothingSpline`
* Update the documentation
**Notes**
In this release the spline representation (the array of spline coefficients) has been changed
according to `PPoly`/`NdPPoly`.
See SciPy [PPoly](https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.PPoly.html)
and [NdPPoly](https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.NdPPoly.html) documentation for details.
## v0.11.0 (28.03.2020)
* Internal re-design `SplinePPForm` and `NdGridSplinePPForm` classes [#17](https://github.com/espdev/csaps/issues/17):
- Remove `shape` and `axis` properties and reshaping data in these classes
- `NdGridSplinePPForm` coefficients array for 1D grid now is 1-d instead of 2-d
* Refactoring the code and decrease memory consumption
* Add `overload` type-hints for `csaps` function signatures
## v0.10.1 (19.03.2020)
* Fix call of `numpy.pad` function for numpy <1.17 [#15](https://github.com/espdev/csaps/issues/15)
## v0.10.0 (18.02.2020)
* Significant performance improvements for make/evaluate splines and memory consumption optimization
* Change format for storing spline coefficients (reshape coeffs array) to improve performance
* Add shape property to `SplinePPForm`/`NdGridSplinePPForm` and axis property to `SplinePPForm`
* Fix issues with the smoothing factor in nd-grid case: inverted ordering and unnable to use 0.0 value
* Update documentation
## v0.9.0 (21.01.2020)
* Drop support of Python 3.5
* `weights`, `smooth` and `axis` arguments in `csaps` function are keyword-only now
* `UnivariateCubicSmoothingSpline` and `MultivariateCubicSmoothingSpline` classes are deprecated
and will be removed in 1.0.0 version. Use `CubicSmoothingSpline` instead.
## v0.8.0 (13.01.2020)
* Add `csaps` function that can be used as the main API
* Refactor the internal structure of the package
* Add the [documentation](https://csaps.readthedocs.io)
**Attention**
This is the last version that supports Python 3.5.
The next versions will support Python 3.6 or above.
## v0.7.0 (19.09.2019)
* Add Generic-based type-hints and mypy-compatibility
## v0.6.1 (13.09.2019)
* A slight refactoring and extra data copies removing
## v0.6.0 (12.09.2019)
* Add "axis" parameter for univariate/multivariate cases
## v0.5.0 (10.06.2019)
* Reorganize the project to package-based structure
* Add the interface class for all smoothing spline classes
## v0.4.2 (07.09.2019)
* FIX: "smooth" value is 0.0 was not used
## v0.4.1 (30.05.2019)
* First PyPI release
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633430830.0
csaps-1.1.0/csaps.egg-info/SOURCES.txt 0000666 0000000 0000000 00000000534 00000000000 015446 0 ustar 00 0000000 0000000 CHANGELOG.md
LICENSE
MANIFEST.in
README.md
setup.py
csaps/__init__.py
csaps/_base.py
csaps/_reshape.py
csaps/_shortcut.py
csaps/_sspndg.py
csaps/_sspumv.py
csaps/_types.py
csaps/_version.py
csaps/py.typed
csaps.egg-info/PKG-INFO
csaps.egg-info/SOURCES.txt
csaps.egg-info/dependency_links.txt
csaps.egg-info/requires.txt
csaps.egg-info/top_level.txt ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633430830.0
csaps-1.1.0/csaps.egg-info/dependency_links.txt 0000666 0000000 0000000 00000000001 00000000000 017626 0 ustar 00 0000000 0000000
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633430830.0
csaps-1.1.0/csaps.egg-info/requires.txt 0000666 0000000 0000000 00000000210 00000000000 016151 0 ustar 00 0000000 0000000 numpy<2,>=1.12.0
scipy<2,>=1.0.0
[docs]
sphinx<5,>=3.0.0
matplotlib>=3.1
numpydoc
m2r2
[tests]
pytest
coverage<7
pytest-cov
coveralls
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633430830.0
csaps-1.1.0/csaps.egg-info/top_level.txt 0000666 0000000 0000000 00000000006 00000000000 016306 0 ustar 00 0000000 0000000 csaps
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1633430830.6824074
csaps-1.1.0/setup.cfg 0000666 0000000 0000000 00000000052 00000000000 012573 0 ustar 00 0000000 0000000 [egg_info]
tag_build =
tag_date = 0
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1633428768.0
csaps-1.1.0/setup.py 0000666 0000000 0000000 00000004417 00000000000 012475 0 ustar 00 0000000 0000000 # -*- coding: utf-8 -*-
import pathlib
from setuptools import setup
ROOT_DIR = pathlib.Path(__file__).parent
def _get_version():
about = {}
ver_mod = ROOT_DIR / 'csaps' / '_version.py'
exec(ver_mod.read_text(), about)
return about['__version__']
def _get_long_description():
readme = ROOT_DIR / 'README.md'
changelog = ROOT_DIR / 'CHANGELOG.md'
return '{}\n{}'.format(
readme.read_text(encoding='utf-8'),
changelog.read_text(encoding='utf-8')
)
setup(
name='csaps',
version=_get_version(),
packages=['csaps'],
python_requires='>=3.6, <4',
install_requires=[
'numpy >=1.12.0, <2',
'scipy >=1.0.0, <2',
],
extras_require={
'docs': ['sphinx >=3.0.0, <5', 'matplotlib >=3.1', 'numpydoc', 'm2r2'],
'tests': ['pytest', 'coverage <7', 'pytest-cov', 'coveralls'],
},
package_data={"csaps": ["py.typed"]},
url='https://github.com/espdev/csaps',
project_urls={
'Documentation': 'https://csaps.readthedocs.io',
'Code': 'https://github.com/espdev/csaps',
'Issue tracker': 'https://github.com/espdev/csaps/issues',
},
license='MIT',
author='Eugene Prilepin',
author_email='esp.home@gmail.com',
description='Cubic spline approximation (smoothing)',
long_description=_get_long_description(),
long_description_content_type='text/markdown',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'Intended Audience :: Science/Research',
'Topic :: Scientific/Engineering',
'Topic :: Scientific/Engineering :: Mathematics',
'Topic :: Software Development :: Libraries',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python',
'Programming Language :: Python :: 3 :: Only',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: Implementation :: CPython',
],
)