From 876db0402a2f43e964bae1e4ac18876c673d6f0c Mon Sep 17 00:00:00 2001 From: Eugene Prilepin Date: Sun, 19 Jan 2020 23:07:03 +0300 Subject: [PATCH 01/12] Bump version to 0.9.0.dev0 --- csaps/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csaps/_version.py b/csaps/_version.py index 0c6d7b8..9d58690 100644 --- a/csaps/_version.py +++ b/csaps/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -__version__ = '0.8.0' +__version__ = '0.9.0.dev0' From 92485169cb55841b5c90fc3172dcd2a9e92b4371 Mon Sep 17 00:00:00 2001 From: Eugene Prilepin Date: Sun, 19 Jan 2020 23:10:27 +0300 Subject: [PATCH 02/12] Bump supported python version to >=3.6 --- .travis.yml | 2 -- README.md | 2 +- docs/index.rst | 2 +- setup.py | 3 +-- tox.ini | 2 +- 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 861dbb4..19142c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,6 @@ language: python matrix: include: - - python: "3.5" - env: TOXENV=py35-pytest - python: "3.6" env: TOXENV=py36-pytest - python: "3.7" diff --git a/README.md b/README.md index ccbb7ec..171f8fe 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ ## Installation -Python 3.5 or above is supported. +Python 3.6 or above is supported. ``` pip install -U csaps diff --git a/docs/index.rst b/docs/index.rst index 1e784a0..63a33a7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -41,7 +41,7 @@ You can install and update csaps using pip: The module depends only on NumPy and SciPy. -Python 3.5 or above is supported. +Python 3.6 or above is supported. Content ------- diff --git a/setup.py b/setup.py index 5518fed..4af1635 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ def _get_long_description(): name='csaps', version=_get_version(), packages=['csaps'], - python_requires='>=3.5, <4', + python_requires='>=3.6, <4', install_requires=[ 'numpy >=0.12.1, <1.20.0', 'scipy >=0.19.1, <1.6.0', @@ -67,7 +67,6 @@ def _get_long_description(): 'Programming Language :: Python', 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', diff --git a/tox.ini b/tox.ini index 7d9429d..091605d 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py{35,36,37,38}-pytest-coverage, flake8 +envlist = py{36,37,38}-pytest-coverage, flake8 [testenv] deps = pytest From 3f050aea7f2d84b8e6d6758b1a8207dfa79c104b Mon Sep 17 00:00:00 2001 From: Eugene Prilepin Date: Sun, 19 Jan 2020 23:24:59 +0300 Subject: [PATCH 03/12] Refactoring - use namedtuple based class with annotations - use relative imports --- csaps/_base.py | 2 +- csaps/_shortcut.py | 20 +++++++++++--------- csaps/_sspndg.py | 6 +++--- csaps/_sspumv.py | 6 +++--- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/csaps/_base.py b/csaps/_base.py index 25bc3f9..177018b 100644 --- a/csaps/_base.py +++ b/csaps/_base.py @@ -10,7 +10,7 @@ import numpy as np -from csaps._types import TData, TProps, TSmooth, TXi, TSpline +from ._types import TData, TProps, TSmooth, TXi, TSpline class SplinePPFormBase(abc.ABC, ty.Generic[TData, TProps]): diff --git a/csaps/_shortcut.py b/csaps/_shortcut.py index d42c7d5..70bfcea 100644 --- a/csaps/_shortcut.py +++ b/csaps/_shortcut.py @@ -10,10 +10,10 @@ import numpy as np -from csaps._base import ISmoothingSpline -from csaps._sspumv import UnivariateCubicSmoothingSpline -from csaps._sspndg import ndgrid_prepare_data_sites, NdGridCubicSmoothingSpline -from csaps._types import ( +from ._base import ISmoothingSpline +from ._sspumv import UnivariateCubicSmoothingSpline +from ._sspndg import ndgrid_prepare_data_sites, NdGridCubicSmoothingSpline +from ._types import ( UnivariateDataType, UnivariateVectorizedDataType, NdGridDataType, @@ -25,11 +25,13 @@ _WeightsDataType = Optional[Union[UnivariateDataType, NdGridDataType]] _SmoothDataType = Optional[Union[float, Sequence[Optional[float]]]] -AutoSmoothingResult = NamedTuple('AutoSmoothingResult', [ - ('values', _YDataType), - ('smooth', _SmoothDataType), -]) -"""The result for auto smoothing for `csaps` function""" + +class AutoSmoothingResult(NamedTuple): + """The result for auto smoothing for `csaps` function""" + + values: _YDataType + smooth: _SmoothDataType + _ReturnType = Union[ _YDataType, diff --git a/csaps/_sspndg.py b/csaps/_sspndg.py index e339931..f2b9958 100644 --- a/csaps/_sspndg.py +++ b/csaps/_sspndg.py @@ -10,9 +10,9 @@ import numpy as np -from csaps._base import SplinePPFormBase, ISmoothingSpline -from csaps._types import UnivariateDataType, NdGridDataType -from csaps._sspumv import SplinePPForm, UnivariateCubicSmoothingSpline +from ._base import SplinePPFormBase, ISmoothingSpline +from ._types import UnivariateDataType, NdGridDataType +from ._sspumv import SplinePPForm, UnivariateCubicSmoothingSpline def ndgrid_prepare_data_sites(data, name) -> ty.Tuple[np.ndarray, ...]: diff --git a/csaps/_sspumv.py b/csaps/_sspumv.py index b5db301..cb00da8 100644 --- a/csaps/_sspumv.py +++ b/csaps/_sspumv.py @@ -11,9 +11,9 @@ import scipy.sparse as sp import scipy.sparse.linalg as la -from csaps._base import SplinePPFormBase, ISmoothingSpline -from csaps._types import UnivariateDataType, UnivariateVectorizedDataType, MultivariateDataType -from csaps._utils import from_2d, to_2d +from ._base import SplinePPFormBase, ISmoothingSpline +from ._types import UnivariateDataType, UnivariateVectorizedDataType, MultivariateDataType +from ._utils import from_2d, to_2d class SplinePPForm(SplinePPFormBase[np.ndarray, int]): From c84aec3c62724a47571be23940cbc48f5d1cdaf6 Mon Sep 17 00:00:00 2001 From: Eugene Prilepin Date: Sun, 19 Jan 2020 23:27:43 +0300 Subject: [PATCH 04/12] Rename 'utils' module to 'reshape' --- csaps/{_utils.py => _reshape.py} | 0 csaps/_sspumv.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename csaps/{_utils.py => _reshape.py} (100%) diff --git a/csaps/_utils.py b/csaps/_reshape.py similarity index 100% rename from csaps/_utils.py rename to csaps/_reshape.py diff --git a/csaps/_sspumv.py b/csaps/_sspumv.py index cb00da8..f8c6dd2 100644 --- a/csaps/_sspumv.py +++ b/csaps/_sspumv.py @@ -13,7 +13,7 @@ from ._base import SplinePPFormBase, ISmoothingSpline from ._types import UnivariateDataType, UnivariateVectorizedDataType, MultivariateDataType -from ._utils import from_2d, to_2d +from ._reshape import from_2d, to_2d class SplinePPForm(SplinePPFormBase[np.ndarray, int]): From 107b7747dd8b7e01cf518f13896f1989fca890c6 Mon Sep 17 00:00:00 2001 From: Eugene Prilepin Date: Sun, 19 Jan 2020 23:29:19 +0300 Subject: [PATCH 05/12] Use keyword-only args in 'csaps' function - weights, smooth, axis --- csaps/_shortcut.py | 1 + 1 file changed, 1 insertion(+) diff --git a/csaps/_shortcut.py b/csaps/_shortcut.py index 70bfcea..d6f9a65 100644 --- a/csaps/_shortcut.py +++ b/csaps/_shortcut.py @@ -43,6 +43,7 @@ class AutoSmoothingResult(NamedTuple): def csaps(xdata: _XDataType, ydata: _YDataType, xidata: _XiDataType = None, + *, weights: _WeightsDataType = None, smooth: _SmoothDataType = None, axis: Optional[int] = None) -> _ReturnType: From f4e4d329227366fba33039bfc861e3f453a61b01 Mon Sep 17 00:00:00 2001 From: Eugene Prilepin Date: Mon, 20 Jan 2020 04:05:59 +0300 Subject: [PATCH 06/12] Add 'CubicSmoothingSpline' class Now classes 'UnivariateCubicSmoothingSpline' and 'MultivariateCubicSmoothingSpline' are deprecated. --- csaps/__init__.py | 2 ++ csaps/_shortcut.py | 4 ++-- csaps/_sspndg.py | 6 ++--- csaps/_sspumv.py | 51 +++++++++++++++++++++++++++++++++++++--- tests/test_univariate.py | 4 ++-- 5 files changed, 57 insertions(+), 10 deletions(-) diff --git a/csaps/__init__.py b/csaps/__init__.py index 89758a8..adc8bd9 100644 --- a/csaps/__init__.py +++ b/csaps/__init__.py @@ -13,6 +13,7 @@ ) from csaps._sspumv import ( SplinePPForm, + CubicSmoothingSpline, UnivariateCubicSmoothingSpline, MultivariateCubicSmoothingSpline, ) @@ -38,6 +39,7 @@ 'ISmoothingSpline', 'SplinePPForm', 'NdGridSplinePPForm', + 'CubicSmoothingSpline', 'UnivariateCubicSmoothingSpline', 'MultivariateCubicSmoothingSpline', 'NdGridCubicSmoothingSpline', diff --git a/csaps/_shortcut.py b/csaps/_shortcut.py index d6f9a65..0f9eb4a 100644 --- a/csaps/_shortcut.py +++ b/csaps/_shortcut.py @@ -11,7 +11,7 @@ import numpy as np from ._base import ISmoothingSpline -from ._sspumv import UnivariateCubicSmoothingSpline +from ._sspumv import CubicSmoothingSpline from ._sspndg import ndgrid_prepare_data_sites, NdGridCubicSmoothingSpline from ._types import ( UnivariateDataType, @@ -154,7 +154,7 @@ def csaps(xdata: _XDataType, if umv: axis = -1 if axis is None else axis - sp = UnivariateCubicSmoothingSpline(xdata, ydata, weights, smooth, axis) + sp = CubicSmoothingSpline(xdata, ydata, weights, smooth, axis) else: sp = NdGridCubicSmoothingSpline(xdata, ydata, weights, smooth) diff --git a/csaps/_sspndg.py b/csaps/_sspndg.py index f2b9958..42acc25 100644 --- a/csaps/_sspndg.py +++ b/csaps/_sspndg.py @@ -12,7 +12,7 @@ from ._base import SplinePPFormBase, ISmoothingSpline from ._types import UnivariateDataType, NdGridDataType -from ._sspumv import SplinePPForm, UnivariateCubicSmoothingSpline +from ._sspumv import SplinePPForm, CubicSmoothingSpline def ndgrid_prepare_data_sites(data, name) -> ty.Tuple[np.ndarray, ...]: @@ -224,8 +224,8 @@ def _make_spline(self, smooth: ty.List[ty.Optional[float]]) -> ty.Tuple[NdGridSp shape_i = (np.prod(sizey[:-1]), sizey[-1]) ydata_i = ydata.reshape(shape_i, order='F') - s = UnivariateCubicSmoothingSpline( - self._xdata[i], ydata_i, self._weights[i], smooth[i]) + s = CubicSmoothingSpline( + self._xdata[i], ydata_i, weights=self._weights[i], smooth=smooth[i]) _smooth.append(s.smooth) sizey[-1] = s.spline.pieces * s.spline.order diff --git a/csaps/_sspumv.py b/csaps/_sspumv.py index f8c6dd2..ff017c0 100644 --- a/csaps/_sspumv.py +++ b/csaps/_sspumv.py @@ -6,13 +6,14 @@ """ import typing as ty +import warnings import numpy as np import scipy.sparse as sp import scipy.sparse.linalg as la from ._base import SplinePPFormBase, ISmoothingSpline -from ._types import UnivariateDataType, UnivariateVectorizedDataType, MultivariateDataType +from ._types import UnivariateDataType, UnivariateVectorizedDataType, MultivariateDataType, TSmooth, TSpline, TXi from ._reshape import from_2d, to_2d @@ -113,8 +114,10 @@ def evaluate(self, xi: np.ndarray) -> np.ndarray: return values -class UnivariateCubicSmoothingSpline(ISmoothingSpline[SplinePPForm, float, UnivariateDataType]): - """Univariate cubic smoothing spline +class CubicSmoothingSpline(ISmoothingSpline[SplinePPForm, float, UnivariateDataType]): + """Cubic smoothing spline + + The cubic spline implementation for univariate/multivariate data. Parameters ---------- @@ -310,6 +313,41 @@ def _make_spline(self, smooth: ty.Optional[float]) -> ty.Tuple[SplinePPForm, flo return spline, p +class UnivariateCubicSmoothingSpline(ISmoothingSpline[SplinePPForm, float, UnivariateDataType]): + __doc__ = CubicSmoothingSpline.__doc__ + + def __init__(self, + xdata: UnivariateDataType, + ydata: UnivariateVectorizedDataType, + weights: ty.Optional[UnivariateDataType] = None, + smooth: ty.Optional[float] = None, + axis: int = -1) -> None: + with warnings.catch_warnings(): + warnings.simplefilter('always', DeprecationWarning) + warnings.warn( + "'UnivariateCubicSmoothingSpline' class is deprecated " + "and will be removed in the future version. " + "Use 'CubicSmoothingSpline' class instead.", stacklevel=2) + + self._cssp = CubicSmoothingSpline( + xdata, ydata, weights=weights, smooth=smooth, axis=axis) + + @property + def smooth(self) -> TSmooth: + return self._cssp.smooth + + @property + def spline(self) -> TSpline: + return self._cssp.spline + + def __call__(self, xi: TXi) -> np.ndarray: + return self._cssp(xi) + + +# For case isinstance(CubicSmoothingSpline(...), UnivariateCubicSmoothingSpline) +UnivariateCubicSmoothingSpline.register(CubicSmoothingSpline) + + class MultivariateCubicSmoothingSpline(ISmoothingSpline[SplinePPForm, float, UnivariateDataType]): """Multivariate parametrized cubic smoothing spline @@ -379,6 +417,13 @@ def __init__(self, smooth: ty.Optional[float] = None, axis: int = -1): + with warnings.catch_warnings(): + warnings.simplefilter('always', DeprecationWarning) + warnings.warn( + "'MultivariateCubicSmoothingSpline' class is deprecated " + "and will be removed in the future version. " + "Use 'CubicSmoothingSpline' class instead.", stacklevel=2) + ydata = ty.cast(np.ndarray, np.asarray(ydata, dtype=np.float64)) if tdata is None: diff --git a/tests/test_univariate.py b/tests/test_univariate.py index 306c4f0..e9fda3f 100644 --- a/tests/test_univariate.py +++ b/tests/test_univariate.py @@ -23,7 +23,7 @@ ]) def test_invalid_data(x, y, w): with pytest.raises(ValueError): - csaps.UnivariateCubicSmoothingSpline(x, y, w) + csaps.UnivariateCubicSmoothingSpline(x, y, weights=w) @pytest.mark.parametrize('y', [ @@ -238,7 +238,7 @@ def test_weighted(w, yid): y = [2., 4., 5., 7.] xi = np.linspace(1., 6., 10) - sp = csaps.UnivariateCubicSmoothingSpline(x, y, w) + sp = csaps.UnivariateCubicSmoothingSpline(x, y, weights=w) yi = sp(xi) np.testing.assert_allclose(yi, yid) From 70224c4bcdbea3d0f7b507bed3501e1a813c07aa Mon Sep 17 00:00:00 2001 From: Eugene Prilepin Date: Mon, 20 Jan 2020 07:34:39 +0300 Subject: [PATCH 07/12] Update doc --- docs/api.rst | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index f85dfe5..14e8fc6 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -15,8 +15,7 @@ Summary AutoSmoothingResult ISmoothingSpline - UnivariateCubicSmoothingSpline - MultivariateCubicSmoothingSpline + CubicSmoothingSpline NdGridCubicSmoothingSpline SplinePPFormBase @@ -39,13 +38,7 @@ Main API Object-Oriented API ------------------- -.. autoclass:: UnivariateCubicSmoothingSpline - :members: - :special-members: __call__ - ----- - -.. autoclass:: MultivariateCubicSmoothingSpline +.. autoclass:: CubicSmoothingSpline :members: :special-members: __call__ From 7b898ff9473c20f6835619c4867e657edfaad885 Mon Sep 17 00:00:00 2001 From: Eugene Prilepin Date: Tue, 21 Jan 2020 14:52:51 +0300 Subject: [PATCH 08/12] Use f-strings and some fixes --- csaps/_base.py | 16 +++++++--------- csaps/_reshape.py | 4 ++-- csaps/_shortcut.py | 7 +++++-- csaps/_sspndg.py | 26 ++++++++++---------------- csaps/_sspumv.py | 15 ++++++--------- 5 files changed, 30 insertions(+), 38 deletions(-) diff --git a/csaps/_base.py b/csaps/_base.py index 177018b..aa3ddb3 100644 --- a/csaps/_base.py +++ b/csaps/_base.py @@ -89,15 +89,13 @@ def evaluate(self, xi: TData) -> np.ndarray: def __repr__(self): return ( - '{}\n' - ' breaks: {}\n' - ' coeffs: {} shape\n' - ' pieces: {}\n' - ' order: {}\n' - ' ndim: {}\n' - ).format( - type(self).__name__, self.breaks, self.coeffs.shape, - self.pieces, self.order, self.ndim) + f'{type(self).__name__}\n' + f' breaks: {self.breaks}\n' + f' coeffs: {self.coeffs.shape} shape\n' + f' pieces: {self.pieces}\n' + f' order: {self.order}\n' + f' ndim: {self.ndim}\n' + ) class ISmoothingSpline(abc.ABC, ty.Generic[TSpline, TSmooth, TXi]): diff --git a/csaps/_reshape.py b/csaps/_reshape.py index c5c71e0..0657ecf 100644 --- a/csaps/_reshape.py +++ b/csaps/_reshape.py @@ -65,7 +65,7 @@ def to_2d(arr: np.ndarray, axis: int) -> np.ndarray: axis = arr.ndim + axis if axis < 0 else axis if axis >= arr.ndim: - raise ValueError('axis {} is out of array axes {}'.format(axis, arr.ndim)) + raise ValueError(f'axis {axis} is out of array axes {arr.ndim}') tr_axes = list(range(arr.ndim)) tr_axes.pop(axis) @@ -136,7 +136,7 @@ def from_2d(arr: np.ndarray, shape: ty.Sequence[int], axis: int) -> np.ndarray: axis = ndim + axis if axis < 0 else axis if axis >= ndim: - raise ValueError('axis {} is out of N-D array axes {}'.format(axis, ndim)) + raise ValueError(f'axis {axis} is out of N-D array axes {ndim}') new_shape = list(shape) new_shape.pop(axis) diff --git a/csaps/_shortcut.py b/csaps/_shortcut.py index 0f9eb4a..fee3b53 100644 --- a/csaps/_shortcut.py +++ b/csaps/_shortcut.py @@ -30,7 +30,10 @@ class AutoSmoothingResult(NamedTuple): """The result for auto smoothing for `csaps` function""" values: _YDataType + """Smoothed data values""" + smooth: _SmoothDataType + """The calculated smoothing parameter""" _ReturnType = Union[ @@ -108,7 +111,7 @@ def csaps(xdata: _XDataType, ssp_obj : ISmoothingSpline Smoothing spline object if ``xidata`` was not set: - - :class:`UnivariateCubicSmoothingSpline` instance for univariate/multivariate data + - :class:`CubicSmoothingSpline` instance for univariate/multivariate data - :class:`NdGridCubicSmoothingSpline` instance for nd-gridded data Examples @@ -137,7 +140,7 @@ def csaps(xdata: _XDataType, See Also -------- - UnivariateCubicSmoothingSpline + CubicSmoothingSpline NdGridCubicSmoothingSpline """ diff --git a/csaps/_sspndg.py b/csaps/_sspndg.py index 42acc25..4a08d56 100644 --- a/csaps/_sspndg.py +++ b/csaps/_sspndg.py @@ -17,17 +17,16 @@ def ndgrid_prepare_data_sites(data, name) -> ty.Tuple[np.ndarray, ...]: if not isinstance(data, c_abc.Sequence): - raise TypeError("'{}' must be a sequence of the vectors.".format(name)) + raise TypeError(f"'{name}' must be a sequence of the vectors.") data = list(data) for i, di in enumerate(data): di = np.array(di, dtype=np.float64) if di.ndim > 1: - raise ValueError("All '{}' elements must be a vector.".format(name)) + raise ValueError(f"All '{name}' elements must be a vector.") if di.size < 2: - raise ValueError( - "'{}' must contain at least 2 data points.".format(name)) + raise ValueError(f"'{name}' must contain at least 2 data points.") data[i] = di return tuple(data) @@ -165,13 +164,11 @@ def _prepare_data(cls, xdata, ydata, weights, smooth): data_ndim = len(xdata) if ydata.ndim != data_ndim: - raise ValueError( - 'ydata must have dimension {} according to xdata'.format(data_ndim)) + raise ValueError(f'ydata must have dimension {data_ndim} according to xdata') for yd, xs in zip(ydata.shape, map(len, xdata)): if yd != xs: - raise ValueError( - 'ydata ({}) and xdata ({}) dimension size mismatch'.format(yd, xs)) + raise ValueError(f'ydata ({yd}) and xdata ({xs}) dimension size mismatch') if not weights: weights = [None] * data_ndim @@ -179,14 +176,12 @@ def _prepare_data(cls, xdata, ydata, weights, smooth): weights = ndgrid_prepare_data_sites(weights, 'weights') if len(weights) != data_ndim: - raise ValueError( - 'weights ({}) and xdata ({}) dimensions mismatch'.format(len(weights), data_ndim)) + raise ValueError(f'weights ({len(weights)}) and xdata ({data_ndim}) dimensions mismatch') for w, x in zip(weights, xdata): if w is not None: if w.size != x.size: - raise ValueError( - 'weights ({}) and xdata ({}) dimension size mismatch'.format(w, x)) + raise ValueError(f'weights ({w}) and xdata ({x}) dimension size mismatch') if not smooth: smooth = [None] * data_ndim @@ -198,8 +193,8 @@ def _prepare_data(cls, xdata, ydata, weights, smooth): if len(smooth) != data_ndim: raise ValueError( - 'Number of smoothing parameter values must be equal ' - 'number of dimensions ({})'.format(data_ndim)) + f'Number of smoothing parameter values must ' + f'be equal number of dimensions ({data_ndim})') return xdata, ydata, weights, smooth @@ -209,8 +204,7 @@ def __call__(self, xi: NdGridDataType) -> np.ndarray: xi = ndgrid_prepare_data_sites(xi, 'xi') if len(xi) != self._ndim: - raise ValueError( - 'xi ({}) and xdata ({}) dimensions mismatch'.format(len(xi), self._ndim)) + raise ValueError(f'xi ({len(xi)}) and xdata ({self._ndim}) dimensions mismatch') return self._spline.evaluate(xi) diff --git a/csaps/_sspumv.py b/csaps/_sspumv.py index ff017c0..0defc79 100644 --- a/csaps/_sspumv.py +++ b/csaps/_sspumv.py @@ -205,8 +205,8 @@ def _prepare_data(xdata, ydata, weights, axis): if yshape[axis] != xdata.size: raise ValueError( - '"ydata" data must be a 1-D or N-D array with shape[{}] that is equal to "xdata" size ({})'.format( - axis, xdata.size)) + f'"ydata" data must be a 1-D or N-D array with shape[{axis}] ' + f'that is equal to "xdata" size ({xdata.size})') # Reshape ydata N-D array to 2-D NxM array where N is the data # dimension and M is the number of data points. @@ -217,8 +217,7 @@ def _prepare_data(xdata, ydata, weights, axis): else: weights = np.asarray(weights, dtype=np.float64) if weights.size != xdata.size: - raise ValueError( - 'Weights vector size must be equal of xdata size') + raise ValueError('Weights vector size must be equal of xdata size') return xdata, ydata, weights, yshape @@ -241,8 +240,7 @@ def _make_spline(self, smooth: ty.Optional[float]) -> ty.Tuple[SplinePPForm, flo dx = np.diff(self._xdata) if not all(dx > 0): - raise ValueError( - 'Items of xdata vector must satisfy the condition: x1 < x2 < ... < xN') + raise ValueError('Items of xdata vector must satisfy the condition: x1 < x2 < ... < xN') dy = np.diff(self._ydata, axis=1) dy_dx = dy / dx @@ -432,13 +430,12 @@ def __init__(self, tdata = ty.cast(np.ndarray, np.asarray(tdata, dtype=np.float64)) if tdata.size != ydata.shape[-1]: - raise ValueError('"tdata" size must be equal to "ydata" shape[{}] size ({})'.format( - axis, ydata.shape[axis])) + raise ValueError(f'"tdata" size must be equal to "ydata" shape[{axis}] size ({ydata.shape[axis]})') self._tdata = tdata # Use vectorization for compute spline for every dimension from t - self._univariate_spline = UnivariateCubicSmoothingSpline( + self._univariate_spline = CubicSmoothingSpline( xdata=tdata, ydata=ydata, weights=weights, From 536332a52ed115e1cb14be1c84da859928d59571 Mon Sep 17 00:00:00 2001 From: Eugene Prilepin Date: Tue, 21 Jan 2020 14:53:36 +0300 Subject: [PATCH 09/12] Bump version to 0.9.0 --- CHANGELOG.md | 7 +++++++ csaps/_version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ce48e9..60d23ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v0.9.0 + +* 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 * Add `csaps` function that can be used as the main API diff --git a/csaps/_version.py b/csaps/_version.py index 9d58690..d452437 100644 --- a/csaps/_version.py +++ b/csaps/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -__version__ = '0.9.0.dev0' +__version__ = '0.9.0' From a6d296ac596dca0f41a26cc3b98d14748201bc0f Mon Sep 17 00:00:00 2001 From: Eugene Prilepin Date: Tue, 21 Jan 2020 15:19:06 +0300 Subject: [PATCH 10/12] add 'nocover' for some if blocks --- csaps/_sspumv.py | 6 +++--- setup.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/csaps/_sspumv.py b/csaps/_sspumv.py index 0defc79..0b37e80 100644 --- a/csaps/_sspumv.py +++ b/csaps/_sspumv.py @@ -164,7 +164,7 @@ def __call__(self, xi: UnivariateDataType) -> np.ndarray: """ xi = ty.cast(np.ndarray, np.asarray(xi, dtype=np.float64)) - if xi.ndim > 1: + if xi.ndim > 1: # pragma: no cover raise ValueError('"xi" data must be a 1-d array.') return self._spline.evaluate(xi) @@ -239,7 +239,7 @@ def _make_spline(self, smooth: ty.Optional[float]) -> ty.Tuple[SplinePPForm, flo pcount = self._xdata.size dx = np.diff(self._xdata) - if not all(dx > 0): + if not all(dx > 0): # pragma: no cover raise ValueError('Items of xdata vector must satisfy the condition: x1 < x2 < ... < xN') dy = np.diff(self._ydata, axis=1) @@ -429,7 +429,7 @@ def __init__(self, tdata = ty.cast(np.ndarray, np.asarray(tdata, dtype=np.float64)) - if tdata.size != ydata.shape[-1]: + if tdata.size != ydata.shape[-1]: # pragma: no cover raise ValueError(f'"tdata" size must be equal to "ydata" shape[{axis}] size ({ydata.shape[axis]})') self._tdata = tdata diff --git a/setup.py b/setup.py index 4af1635..e3c8830 100644 --- a/setup.py +++ b/setup.py @@ -41,6 +41,7 @@ def _get_long_description(): ], 'tests': [ 'pytest', + 'coverage', ], }, package_data={"csaps": ["py.typed"]}, From ce0cc850d6f205f7b63650299c89a3174ac1ab38 Mon Sep 17 00:00:00 2001 From: Eugene Prilepin Date: Tue, 21 Jan 2020 16:20:29 +0300 Subject: [PATCH 11/12] Increase test coverage --- csaps/_base.py | 2 +- csaps/_reshape.py | 4 ++-- csaps/_sspndg.py | 2 +- csaps/_sspumv.py | 6 +++--- tests/test_multivariate.py | 4 ++++ tests/{test_gridded.py => test_ndgrid.py} | 11 +++++++++-- tests/test_shortcut.py | 12 +++++++++--- tests/test_univariate.py | 1 + 8 files changed, 30 insertions(+), 12 deletions(-) rename tests/{test_gridded.py => test_ndgrid.py} (76%) diff --git a/csaps/_base.py b/csaps/_base.py index aa3ddb3..f6e8b21 100644 --- a/csaps/_base.py +++ b/csaps/_base.py @@ -87,7 +87,7 @@ def evaluate(self, xi: TData) -> np.ndarray: Interpolated/smoothed data """ - def __repr__(self): + def __repr__(self): # pragma: no cover return ( f'{type(self).__name__}\n' f' breaks: {self.breaks}\n' diff --git a/csaps/_reshape.py b/csaps/_reshape.py index 0657ecf..893a160 100644 --- a/csaps/_reshape.py +++ b/csaps/_reshape.py @@ -64,7 +64,7 @@ def to_2d(arr: np.ndarray, axis: int) -> np.ndarray: arr = np.asarray(arr) axis = arr.ndim + axis if axis < 0 else axis - if axis >= arr.ndim: + 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)) @@ -135,7 +135,7 @@ def from_2d(arr: np.ndarray, shape: ty.Sequence[int], axis: int) -> np.ndarray: ndim = len(shape) axis = ndim + axis if axis < 0 else axis - if axis >= ndim: + if axis >= ndim: # pragma: no cover raise ValueError(f'axis {axis} is out of N-D array axes {ndim}') new_shape = list(shape) diff --git a/csaps/_sspndg.py b/csaps/_sspndg.py index 4a08d56..a91fe80 100644 --- a/csaps/_sspndg.py +++ b/csaps/_sspndg.py @@ -203,7 +203,7 @@ def __call__(self, xi: NdGridDataType) -> np.ndarray: """ xi = ndgrid_prepare_data_sites(xi, 'xi') - if len(xi) != self._ndim: + if len(xi) != self._ndim: # pragma: no cover raise ValueError(f'xi ({len(xi)}) and xdata ({self._ndim}) dimensions mismatch') return self._spline.evaluate(xi) diff --git a/csaps/_sspumv.py b/csaps/_sspumv.py index 0b37e80..8835109 100644 --- a/csaps/_sspumv.py +++ b/csaps/_sspumv.py @@ -331,14 +331,14 @@ def __init__(self, xdata, ydata, weights=weights, smooth=smooth, axis=axis) @property - def smooth(self) -> TSmooth: + def smooth(self) -> float: return self._cssp.smooth @property - def spline(self) -> TSpline: + def spline(self) -> SplinePPForm: return self._cssp.spline - def __call__(self, xi: TXi) -> np.ndarray: + def __call__(self, xi: UnivariateDataType) -> np.ndarray: return self._cssp(xi) diff --git a/tests/test_multivariate.py b/tests/test_multivariate.py index f01f49a..ded12bc 100644 --- a/tests/test_multivariate.py +++ b/tests/test_multivariate.py @@ -14,4 +14,8 @@ def test_auto_tdata(): t = [0., 3.74165739, 8.10055633, 12.68313203] sp = csaps.MultivariateCubicSmoothingSpline(data) + + assert isinstance(sp.spline, csaps.SplinePPForm) + assert 0 < sp.smooth < 1 + assert sp(t).shape == (3, 4) np.testing.assert_allclose(sp.t, t) diff --git a/tests/test_gridded.py b/tests/test_ndgrid.py similarity index 76% rename from tests/test_gridded.py rename to tests/test_ndgrid.py index 412f840..075fb9a 100644 --- a/tests/test_gridded.py +++ b/tests/test_ndgrid.py @@ -14,7 +14,9 @@ ([[1, 2, 3], [1, 2, 3]], np.ones((3, 3)), [1, 2, 3], None), ([[1, 2, 3], [1, 2, 3]], np.ones((3, 3)), [[1, 2, 3]], None), ([[1, 2, 3], [1, 2, 3]], np.ones((3, 3)), [[1, 2], [1, 2]], None), - ([[1, 2, 3], [1, 2, 3]], np.ones((3, 3)), None, [0.5, 0.4, 0.2]) + ([[1, 2, 3], [1, 2, 3]], np.ones((3, 3)), None, [0.5, 0.4, 0.2]), + (np.array([[1, 2, 3], [4, 5, 6]]), np.ones((3, 3)), None, None), + ([np.arange(6).reshape(2, 3), np.arange(6).reshape(2, 3)], np.ones((6, 6)), None, None), ]) def test_invalid_data(x, y, w, p): with pytest.raises((ValueError, TypeError)): @@ -33,4 +35,9 @@ def test_surface(): noisy = ydata + (np.random.randn(*ydata.shape) * 0.75) sp = csaps.NdGridCubicSmoothingSpline(xdata, noisy) - _ = sp(xdata) + noisy_s = sp(xdata) + + assert isinstance(sp.smooth, tuple) + assert len(sp.smooth) == 2 + assert isinstance(sp.spline, csaps.NdGridSplinePPForm) + assert noisy_s.shape == noisy.shape diff --git a/tests/test_shortcut.py b/tests/test_shortcut.py index f7539bd..e1c4559 100644 --- a/tests/test_shortcut.py +++ b/tests/test_shortcut.py @@ -3,7 +3,7 @@ import pytest import numpy as np -from csaps import csaps, AutoSmoothingResult, UnivariateCubicSmoothingSpline, NdGridCubicSmoothingSpline +from csaps import csaps, AutoSmoothingResult, CubicSmoothingSpline, NdGridCubicSmoothingSpline @pytest.fixture(scope='module') @@ -34,7 +34,7 @@ def data(curve, surface, request): if request.param == 'univariate': x, y = curve xi = np.linspace(x[0], x[-1], 150) - return x, y, xi, 0.85, UnivariateCubicSmoothingSpline + return x, y, xi, 0.85, CubicSmoothingSpline elif request.param == 'ndgrid': x, y = surface @@ -46,9 +46,15 @@ def data(curve, surface, request): 'univariate', 'ndgrid', ], indirect=True) -def test_shortcut_output(data): +@pytest.mark.parametrize('tolist', [True, False]) +def test_shortcut_output(data, tolist): x, y, xi, smooth, sp_cls = data + if tolist and isinstance(x, np.ndarray): + x = x.tolist() + y = y.tolist() + xi = xi.tolist() + yi = csaps(x, y, xi, smooth=smooth) assert isinstance(yi, np.ndarray) diff --git a/tests/test_univariate.py b/tests/test_univariate.py index e9fda3f..947fcde 100644 --- a/tests/test_univariate.py +++ b/tests/test_univariate.py @@ -160,6 +160,7 @@ def test_auto_smooth(): xi = np.linspace(x[0], x[-1], 120) yi = sp(xi) + assert isinstance(sp.spline, csaps.SplinePPForm) np.testing.assert_almost_equal(sp.smooth, 0.996566686) desired_yi = [ From 766735fc878d509214522552294f63fbae6617eb Mon Sep 17 00:00:00 2001 From: Eugene Prilepin Date: Tue, 21 Jan 2020 16:25:31 +0300 Subject: [PATCH 12/12] Remove extra imports --- csaps/_sspumv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csaps/_sspumv.py b/csaps/_sspumv.py index 8835109..0e1581a 100644 --- a/csaps/_sspumv.py +++ b/csaps/_sspumv.py @@ -13,7 +13,7 @@ import scipy.sparse.linalg as la from ._base import SplinePPFormBase, ISmoothingSpline -from ._types import UnivariateDataType, UnivariateVectorizedDataType, MultivariateDataType, TSmooth, TSpline, TXi +from ._types import UnivariateDataType, UnivariateVectorizedDataType, MultivariateDataType from ._reshape import from_2d, to_2d