J'essaie de trouver un python package qui donnerait une option pour adapter des splines de lissage naturelles avec un facteur de lissage sélectionnable par l'utilisateur Y a-t-il une implémentation pour cela? Sinon, comment utiliseriez-vous les ressources disponibles pour l'implémenter vous-même?
Par spline naturelle, j'entends qu'il devrait y avoir une condition selon laquelle la dérivée seconde de la fonction ajustée aux extrémités est zéro (linéaire).
En lissant la spline, je veux dire que la spline ne doit pas être "interpolée" (passant par tous les points de données). Je voudrais décider moi-même du facteur de lissage lambda correct (voir page Wikipedia pour lisser les splines).
scipy.interpolate.CubicSpline [ link ]: Effectue un ajustement de spline naturel (cubique). Interpolation et aucun moyen de lisser les données.
scipy.interpolate.UnivariateSpline [ link ]: Effectue un ajustement de spline avec un facteur de lissage sélectionnable par l'utilisateur. Cependant, il n'y a pas d'option pour rendre les splines naturelles.
Après des heures d'enquête, je n'ai trouvé aucun paquet installable par pip pouvant s'adapter à une spline cubique naturelle avec une douceur contrôlable par l'utilisateur. Cependant, après avoir décidé d'en écrire un moi-même, tout en lisant le sujet, je suis tombé sur un article de blog par l'utilisateur de github madrury . Il a écrit python code capable de produire des modèles de splines cubiques naturelles.
Le code du modèle est disponible ici (NaturalCubicSpline) avec ne licence BSD . Il a également écrit quelques exemples dans un bloc-notes IPython .
Mais comme il s'agit d'Internet et que les liens ont tendance à mourir, je vais copier ici les parties pertinentes du code source + une fonction d'aide (get_natural_cubic_spline_model
) écrit par moi et montre un exemple d'utilisation. La douceur de l'ajustement peut être contrôlée en utilisant différents nombres de nœuds. La position des nœuds peut également être spécifiée par l'utilisateur.
from matplotlib import pyplot as plt
import numpy as np
def func(x):
return 1/(1+25*x**2)
# make example data
x = np.linspace(-1,1,300)
y = func(x) + np.random.normal(0, 0.2, len(x))
# The number of knots can be used to control the amount of smoothness
model_6 = get_natural_cubic_spline_model(x, y, minval=min(x), maxval=max(x), n_knots=6)
model_15 = get_natural_cubic_spline_model(x, y, minval=min(x), maxval=max(x), n_knots=15)
y_est_6 = model_6.predict(x)
y_est_15 = model_15.predict(x)
plt.plot(x, y, ls='', marker='.', label='originals')
plt.plot(x, y_est_6, marker='.', label='n_knots = 6')
plt.plot(x, y_est_15, marker='.', label='n_knots = 15')
plt.legend(); plt.show()
get_natural_cubic_spline_model
import numpy as np
import pandas as pd
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
def get_natural_cubic_spline_model(x, y, minval=None, maxval=None, n_knots=None, knots=None):
"""
Get a natural cubic spline model for the data.
For the knots, give (a) `knots` (as an array) or (b) minval, maxval and n_knots.
If the knots are not directly specified, the resulting knots are equally
space within the *interior* of (max, min). That is, the endpoints are
*not* included as knots.
Parameters
----------
x: np.array of float
The input data
y: np.array of float
The outpur data
minval: float
Minimum of interval containing the knots.
maxval: float
Maximum of the interval containing the knots.
n_knots: positive integer
The number of knots to create.
knots: array or list of floats
The knots.
Returns
--------
model: a model object
The returned model will have following method:
- predict(x):
x is a numpy array. This will return the predicted y-values.
"""
if knots:
spline = NaturalCubicSpline(knots=knots)
else:
spline = NaturalCubicSpline(max=maxval, min=minval, n_knots=n_knots)
p = Pipeline([
('nat_cubic', spline),
('regression', LinearRegression(fit_intercept=True))
])
p.fit(x, y)
return p
class AbstractSpline(BaseEstimator, TransformerMixin):
"""Base class for all spline basis expansions."""
def __init__(self, max=None, min=None, n_knots=None, n_params=None, knots=None):
if knots is None:
if not n_knots:
n_knots = self._compute_n_knots(n_params)
knots = np.linspace(min, max, num=(n_knots + 2))[1:-1]
max, min = np.max(knots), np.min(knots)
self.knots = np.asarray(knots)
@property
def n_knots(self):
return len(self.knots)
def fit(self, *args, **kwargs):
return self
class NaturalCubicSpline(AbstractSpline):
"""Apply a natural cubic basis expansion to an array.
The features created with this basis expansion can be used to fit a
piecewise cubic function under the constraint that the fitted curve is
linear *outside* the range of the knots.. The fitted curve is continuously
differentiable to the second order at all of the knots.
This transformer can be created in two ways:
- By specifying the maximum, minimum, and number of knots.
- By specifying the cutpoints directly.
If the knots are not directly specified, the resulting knots are equally
space within the *interior* of (max, min). That is, the endpoints are
*not* included as knots.
Parameters
----------
min: float
Minimum of interval containing the knots.
max: float
Maximum of the interval containing the knots.
n_knots: positive integer
The number of knots to create.
knots: array or list of floats
The knots.
"""
def _compute_n_knots(self, n_params):
return n_params
@property
def n_params(self):
return self.n_knots - 1
def transform(self, X, **transform_params):
X_spl = self._transform_array(X)
if isinstance(X, pd.Series):
col_names = self._make_names(X)
X_spl = pd.DataFrame(X_spl, columns=col_names, index=X.index)
return X_spl
def _make_names(self, X):
first_name = "{}_spline_linear".format(X.name)
rest_names = ["{}_spline_{}".format(X.name, idx)
for idx in range(self.n_knots - 2)]
return [first_name] + rest_names
def _transform_array(self, X, **transform_params):
X = X.squeeze()
try:
X_spl = np.zeros((X.shape[0], self.n_knots - 1))
except IndexError: # For arrays with only one element
X_spl = np.zeros((1, self.n_knots - 1))
X_spl[:, 0] = X.squeeze()
def d(knot_idx, x):
def ppart(t): return np.maximum(0, t)
def cube(t): return t*t*t
numerator = (cube(ppart(x - self.knots[knot_idx]))
- cube(ppart(x - self.knots[self.n_knots - 1])))
denominator = self.knots[self.n_knots - 1] - self.knots[knot_idx]
return numerator / denominator
for i in range(0, self.n_knots - 2):
X_spl[:, i+1] = (d(i, X) - d(self.n_knots - 2, X)).squeeze()
return X_spl
Vous pouvez utiliser cette implémentation numpy/scipy de spline de lissage cubique naturel pour le lissage des données univariées/multivariées. Le paramètre de lissage doit être dans la plage [0,0, 1,0]. Si nous utilisons un paramètre de lissage égal à 1.0, nous obtenons un interpolant spline cubique naturel sans lissage des données. L'implémentation prend également en charge la vectorisation pour les données univariées.
Exemple univarié:
import numpy as np
import matplotlib.pyplot as plt
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
sp = csaps.UnivariateCubicSmoothingSpline(x, y, smooth=0.85)
xs = np.linspace(x[0], x[-1], 150)
ys = sp(xs)
plt.plot(x, y, 'o', xs, ys, '-')
plt.show()
Exemple bivarié:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import csaps
xdata = [np.linspace(-3, 3, 61), np.linspace(-3.5, 3.5, 51)]
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))
np.random.seed(12345)
noisy = ydata + (np.random.randn(*ydata.shape) * 0.75)
sp = csaps.MultivariateCubicSmoothingSpline(xdata, noisy, smooth=0.988)
ysmth = sp(xdata)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_wireframe(j, i, noisy, linewidths=0.5, color='r')
ax.scatter(j, i, noisy, s=5, c='r')
ax.plot_surface(j, i, ysmth, linewidth=0, alpha=1.0)
plt.show()
Le langage de programmation R offre une très bonne implémentation de splines de lissage cubique naturel. Vous pouvez utiliser les fonctions R dans Python avec rpy2
:
import rpy2.robjects as robjects
r_y = robjects.FloatVector(y_train)
r_x = robjects.FloatVector(x_train)
r_smooth_spline = robjects.r['smooth.spline'] #extract R function# run smoothing function
spline1 = r_smooth_spline(x=r_x, y=r_y, spar=0.7)
ySpline=np.array(robjects.r['predict'](spline1,robjects.FloatVector(x_smooth)).rx2('y'))
plt.plot(x_smooth,ySpline)
Si vous voulez définir directement lambda
: spline1 = r_smooth_spline(x=r_x, y=r_y, lambda=42)
ne fonctionne pas, car lambda
a déjà une autre signification en Python, mais il existe une solution: Comment pour utiliser l'argument lambda de smooth.spline dans RPy SANS Python l'interprétant comme lambda .
Pour exécuter le code, vous devez d'abord définir les données x_train
Et y_train
Et vous pouvez définir x_smooth=np.array(np.linspace(-3,5,1920)).
si vous voulez le tracer entre -3 et 5 en Full- Résolution HD.