Skip to content

Commit 001c1c6

Browse files
authored
Merge 4327197 into 0386282
2 parents 0386282 + 4327197 commit 001c1c6

File tree

4 files changed

+261
-21
lines changed

4 files changed

+261
-21
lines changed

csaps/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,14 @@
2626
MultivariateDataType,
2727
NdGridDataType,
2828
)
29+
from csaps._shortcut import csaps, SmoothingResult
2930

3031
__all__ = [
32+
# Shortcut
33+
'csaps',
34+
'SmoothingResult',
35+
36+
# Classes
3137
'SplinePPFormBase',
3238
'ISmoothingSpline',
3339
'SplinePPForm',

csaps/_shortcut.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
The module provised `csaps` shortcut function for smoothing data
5+
6+
"""
7+
8+
from collections import abc as c_abc
9+
from typing import Optional, Union, Sequence, NamedTuple
10+
11+
import numpy as np
12+
13+
from csaps._base import ISmoothingSpline
14+
from csaps._sspumv import UnivariateCubicSmoothingSpline
15+
from csaps._sspndg import ndgrid_prepare_data_sites, NdGridCubicSmoothingSpline
16+
from csaps._types import (
17+
UnivariateDataType,
18+
UnivariateVectorizedDataType,
19+
NdGridDataType,
20+
)
21+
22+
_XDataType = Union[UnivariateDataType, NdGridDataType]
23+
_YDataType = Union[UnivariateVectorizedDataType, np.ndarray]
24+
_XiDataType = Optional[Union[UnivariateDataType, NdGridDataType]]
25+
_WeightsDataType = Optional[Union[UnivariateDataType, NdGridDataType]]
26+
_SmoothDataType = Optional[Union[float, Sequence[Optional[float]]]]
27+
28+
SmoothingResult = NamedTuple('SmoothingResult', [
29+
('values', _YDataType),
30+
('smooth', _SmoothDataType),
31+
])
32+
33+
_ReturnType = Union[
34+
_YDataType,
35+
SmoothingResult,
36+
ISmoothingSpline,
37+
]
38+
39+
40+
def csaps(xdata: _XDataType,
41+
ydata: _YDataType,
42+
xidata: _XiDataType = None,
43+
weights: _WeightsDataType = None,
44+
smooth: _SmoothDataType = None,
45+
axis: Optional[int] = None) -> _ReturnType:
46+
"""Smooths the univariate/multivariate/gridded data or computes the corresponding splines
47+
48+
This function might be used in procedural code.
49+
50+
Parameters
51+
----------
52+
xdata : np.ndarray, array-like
53+
[required] The data sites ``x1 < x2 < ... < xN``:
54+
- 1-D data vector/sequence (array-like) for univariate/multivariate ydata case
55+
- The sequence of 1-D data vectors for nd-gridded ydata case
56+
57+
ydata : np.ndarray, array-like
58+
[required] The data values:
59+
- 1-D data vector/sequence (array-like) for univariate data case
60+
- N-D array/array-like for multivariate data case
61+
- N-D array for nd-gridded data case
62+
63+
xidata : np.ndarray, array-like, sequence[array-like]
64+
[optional] The data sites for output smoothed data:
65+
- 1-D data vector/sequence (array-like) for univariate/multivariate ydata case
66+
- The sequence of 1-D data vectors for nd-gridded ydata case
67+
If this argument was not set, the function will return computed spline for given data
68+
in `ISmoothingSpline` object.
69+
70+
weights : np.ndarray, array-like, sequence[array-like]
71+
[optional] The weights data vectors:
72+
- 1-D data vector/sequence (array-like) for univariate/multivariate ydata case
73+
- The sequence of 1-D data vectors for nd-gridded ydata case
74+
75+
smooth : float, sequence[float]
76+
[optional] The smoothing factor value(s):
77+
- float value in the range ``[0, 1]`` for univariate/multivariate ydata case
78+
- the sequence of float in the range ``[0, 1]`` or None for nd-gridded ydata case
79+
If this argument was not set or None or sequence with None-items, the function will return
80+
named tuple `SmoothingResult` with computed smoothed data values and smoothing factor value(s).
81+
82+
axis : int
83+
[optional] The ydata axis. Axis along which "ydata" is assumed to be varying.
84+
If this argument was not set the last axis will be used.
85+
Currently, `axis` will be ignored for nd-gridded ydata case.
86+
87+
Returns
88+
-------
89+
yidata : np.ndarray
90+
Smoothed data values if `xidata` and `smooth` were set.
91+
smoothed_data : SmoothingResult
92+
The named tuple with two fileds:
93+
- 'values' -- smoothed data values
94+
- 'smooth' -- computed smoothing factor
95+
This result will be returned if `xidata` was set and `smooth` was not set.
96+
sspobj : ISmoothingSpline
97+
Smoothing spline object if `xidata` was not set:
98+
- `UnivariateCubicSmoothingSpline` instance for univariate/multivariate data
99+
- `NdGridCubicSmoothingSpline` instance for nd-gridded data
100+
101+
Examples
102+
--------
103+
104+
Univariate data smoothing
105+
106+
.. code-block:: python
107+
108+
import numpy as np
109+
from csaps import csaps
110+
111+
x = np.linspace(-5., 5., 25)
112+
y = np.exp(-(x/2.5)**2) + (np.random.rand(25) - 0.2) * 0.3
113+
xi = np.linspace(-5., 5., 150)
114+
115+
# Smooth data with smoothing factor 0.85
116+
yi = csaps(x, y, xi, smooth=0.85)
117+
118+
# Smooth data and compute smoothing factor automatically
119+
yi, smooth = csaps(x, y, xi)
120+
121+
# Do not evaluate the spline, only compute it
122+
sp = csaps(x, y, smooth=0.98)
123+
124+
See Also
125+
--------
126+
127+
`UnivariateCubicSmoothingSpline`
128+
`NdGridCubicSmoothingSpline`
129+
130+
"""
131+
132+
if isinstance(xdata, c_abc.Sequence):
133+
try:
134+
ndgrid_prepare_data_sites(xdata, 'xdata')
135+
except ValueError:
136+
umv = True
137+
else:
138+
umv = False
139+
else:
140+
umv = True
141+
142+
if umv:
143+
axis = -1 if axis is None else axis
144+
sp = UnivariateCubicSmoothingSpline(xdata, ydata, weights, smooth, axis)
145+
else:
146+
sp = NdGridCubicSmoothingSpline(xdata, ydata, weights, smooth)
147+
148+
if xidata is None:
149+
return sp
150+
151+
yidata = sp(xidata)
152+
153+
auto_smooth = smooth is None
154+
if isinstance(smooth, Sequence):
155+
auto_smooth = any(sm is None for sm in smooth)
156+
157+
if auto_smooth:
158+
return SmoothingResult(yidata, sp.smooth)
159+
else:
160+
return yidata

csaps/_sspndg.py

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,24 @@
1515
from csaps._sspumv import SplinePPForm, UnivariateCubicSmoothingSpline
1616

1717

18+
def ndgrid_prepare_data_sites(data, name) -> ty.Tuple[np.ndarray, ...]:
19+
if not isinstance(data, c_abc.Sequence):
20+
raise TypeError("'{}' must be a sequence of the vectors.".format(name))
21+
22+
data = list(data)
23+
24+
for i, di in enumerate(data):
25+
di = np.array(di, dtype=np.float64)
26+
if di.ndim > 1:
27+
raise ValueError("All '{}' elements must be a vector.".format(name))
28+
if di.size < 2:
29+
raise ValueError(
30+
"'{}' must contain at least 2 data points.".format(name))
31+
data[i] = di
32+
33+
return tuple(data)
34+
35+
1836
class NdGridSplinePPForm(SplinePPFormBase[ty.Sequence[np.ndarray], ty.Tuple[int, ...]]):
1937
"""N-D grid spline representation in PP-form
2038
@@ -134,27 +152,9 @@ def spline(self) -> NdGridSplinePPForm:
134152
"""
135153
return self._spline
136154

137-
@staticmethod
138-
def _prepare_grid_vectors(data, name) -> ty.Tuple[np.ndarray, ...]:
139-
if not isinstance(data, c_abc.Sequence):
140-
raise TypeError('{} must be sequence of vectors'.format(name))
141-
142-
data = list(data)
143-
144-
for i, di in enumerate(data):
145-
di = np.array(di, dtype=np.float64)
146-
if di.ndim > 1:
147-
raise ValueError('All {} elements must be vector'.format(name))
148-
if di.size < 2:
149-
raise ValueError(
150-
'{} must contain at least 2 data points'.format(name))
151-
data[i] = di
152-
153-
return tuple(data)
154-
155155
@classmethod
156156
def _prepare_data(cls, xdata, ydata, weights, smooth):
157-
xdata = cls._prepare_grid_vectors(xdata, 'xdata')
157+
xdata = ndgrid_prepare_data_sites(xdata, 'xdata')
158158
data_ndim = len(xdata)
159159

160160
if ydata.ndim != data_ndim:
@@ -169,7 +169,7 @@ def _prepare_data(cls, xdata, ydata, weights, smooth):
169169
if not weights:
170170
weights = [None] * data_ndim
171171
else:
172-
weights = cls._prepare_grid_vectors(weights, 'weights')
172+
weights = ndgrid_prepare_data_sites(weights, 'weights')
173173

174174
if len(weights) != data_ndim:
175175
raise ValueError(
@@ -197,7 +197,7 @@ def _prepare_data(cls, xdata, ydata, weights, smooth):
197197
return xdata, ydata, weights, smooth
198198

199199
def __call__(self, xi: NdGridDataType) -> np.ndarray:
200-
xi = self._prepare_grid_vectors(xi, 'xi')
200+
xi = ndgrid_prepare_data_sites(xi, 'xi')
201201

202202
if len(xi) != self._ndim:
203203
raise ValueError(

tests/test_shortcut.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import pytest
4+
import numpy as np
5+
6+
from csaps import csaps, SmoothingResult, UnivariateCubicSmoothingSpline, NdGridCubicSmoothingSpline
7+
8+
9+
@pytest.fixture(scope='module')
10+
def curve():
11+
np.random.seed(12345)
12+
13+
x = np.linspace(-5., 5., 25)
14+
y = np.exp(-(x / 2.5) ** 2) + (np.random.rand(25) - 0.2) * 0.3
15+
return x, y
16+
17+
18+
@pytest.fixture(scope='module')
19+
def surface():
20+
np.random.seed(12345)
21+
22+
x = [np.linspace(-3, 3, 61), np.linspace(-3.5, 3.5, 51)]
23+
i, j = np.meshgrid(*x, indexing='ij')
24+
25+
y = (3 * (1 - j) ** 2. * np.exp(-(j ** 2) - (i + 1) ** 2)
26+
- 10 * (j / 5 - j ** 3 - i ** 5) * np.exp(-j ** 2 - i ** 2)
27+
- 1 / 3 * np.exp(-(j + 1) ** 2 - i ** 2))
28+
y += np.random.randn(*y.shape) * 0.75
29+
return x, y
30+
31+
32+
@pytest.fixture
33+
def data(curve, surface, request):
34+
if request.param == 'univariate':
35+
x, y = curve
36+
xi = np.linspace(x[0], x[-1], 150)
37+
return x, y, xi, 0.85, UnivariateCubicSmoothingSpline
38+
39+
elif request.param == 'ndgrid':
40+
x, y = surface
41+
42+
return x, y, x, [0.85, 0.85], NdGridCubicSmoothingSpline
43+
44+
45+
@pytest.mark.parametrize('data', [
46+
'univariate',
47+
'ndgrid',
48+
], indirect=True)
49+
def test_shortcut_output(data):
50+
x, y, xi, smooth, sp_cls = data
51+
52+
yi = csaps(x, y, xi, smooth=smooth)
53+
assert isinstance(yi, np.ndarray)
54+
55+
smoothed_data = csaps(x, y, xi)
56+
assert isinstance(smoothed_data, SmoothingResult)
57+
58+
sp = csaps(x, y)
59+
assert isinstance(sp, sp_cls)
60+
61+
62+
@pytest.mark.parametrize('smooth, cls', [
63+
(0.85, np.ndarray),
64+
([0.85, 0.85], np.ndarray),
65+
(None, SmoothingResult),
66+
([None, 0.85], SmoothingResult),
67+
([0.85, None], SmoothingResult),
68+
([None, None], SmoothingResult),
69+
])
70+
def test_shortcut_ndgrid_smooth_output(surface, smooth, cls):
71+
x, y = surface
72+
73+
output = csaps(x, y, x, smooth=smooth)
74+
assert isinstance(output, cls)

0 commit comments

Comments
 (0)