I have two vectors x and y, and I fit them by a smoothing spline fit in matlab. I obtain this:
form: 'pp'
breaks: [15.5649 16.2041 17.0345 18.0489 20.1834 22.5540 24.5158 27.7881 32.5594 36.0827 40.5951]
coefs: [10x4 double]
pieces: 10
order: 4
dim: 1
I need to know the coefficients of the fit in order to reconstruct the fitted curve.
How can I get this information?
As mentioned by #A_C, you can obtain the coefficients from the coefs parameter. You should keep in mind that a spline fits a different polynomial to each region - in your case 10 regions.
As it is quite a lot of work to reconstruct the curve from the coefficients Matlab offers you the ppval function to do this:
x = [3 4 7 9];
y = [2 1 2 0.5];
xx = 0:0.1:10;
pp = spline(x,y);
yy = ppval(pp,xx);
plot(xx,yy);
Alternatively, if you only need to perform one interpolation, why not do it directly:
x = [3 4 7 9];
y = [2 1 2 0.5];
xx = 0:0.1:10;
yy = spline(x,y,xx);
plot(xx,yy);
Related
I need to generate a curve between scatter points then identify the unit normal of the curve at each point. Here is an example of a point cloud
figure
x = [1 2 0 0 1 1 2 3 4 2];
y = [4 6 9 1 1 2 4 9 2 3];
scatter(x,y)
hold on
line(x,y)
xlim([0 4])
ylim([0 10])
NOTE: the 2 points along the y-axis are connected
Instead of a line between the points, I'd like to create a smooth curve. I'm not sure how to do this when points in x and y repeat. An attempt using spline fails. After I know the curve, I need to find the unit normals at each point. How do I go about this?
EDIT:
Basically I want to do what is show here for polyfit in the matlab docs. Assuming that x was unique in my case, this wouldn't be an issue. I could identify the polynomial and then, I believe, determine the unit normals from the polynomial function evaluated at that point. But in my case, the x and y data repeat so a straight forward application doesn't work.
One way to get a smooth path is to treat this as a parametric function and interpolate x and y separately.
x = [1 2 0 0 1 1 2 3 4 2];
y = [4 6 9 1 1 2 4 9 2 3];
t = 1:numel(x);
tq = 1:0.1:t(end);
xq = interp1(t,x,tq,'v5cubic');
yq = interp1(t,y,tq,'v5cubic');
plot(x,y,' ob',xq,yq,'-r');
To estimate the normals you can take the average normal of the two line segments around the sample points. This code is a bit ugly but it gets the job done.
n = zeros(2,numel(x));
for tidx = 1:numel(t)
tt = t(tidx);
idx1 = find(tq <= tt,1,'last');
idx0 = idx1 - 1;
idx2 = idx1 + 1;
if idx0 > 0
n1 = [yq(idx1) - yq(idx0); xq(idx0) - xq(idx1)];
n(:,tidx) = n(:,tidx) + n1/norm(n1);
end
if idx2 <= numel(tq)
n2 = [yq(idx2) - yq(idx1); xq(idx1) - xq(idx2)];
n(:,tidx) = n(:,tidx) + n2/norm(n2);
end
n(:,tidx) = n(:,tidx) / norm(n(:,tidx));
end
plot(x,y,' ob',xq,yq,'-r',[x.' x.'+n(1,:).'].', [y.' y.'+n(2,:).'].',' -k');
axis equal;
If you use pchip instead of v5cubic for the interpolation method then you get more symmetry around the sample points. However, it appears that any sharp turns (90 degrees or greater) are not smoothed.
I am trying to implement a clamped cubic spline with a zero slope (flat extrapolation) at the boundary knots, but I am unable to obtain the desired results.
For instance setting:
x = [3 4 7 9];
y = [2 1 2 0.5];
I can use the CSAPE function to obtain the piecewise polynomial
pp = csape(x,y,'variational');
Next, evaluating the pp in the range [0-10] yields,
xx = 0:0.1:10;
yy =ppval(pp,xx);
plot(xx,yy)
However, this method do not achieve a flat extrapolation outside the [3-9] range (i.e for x<3 all values for y should be 2, and for x>9 all values for y should be 0.5)
Is there any way to achieve the desired result?
Edit: Continuity at the boundary knot should be preserved
I don't think there's any need to use csape, you can just use spline. From the documentation for spline:
If Y is a vector that contains two more values than x has entries, the
first and last value in Y are used as the endslopes for the cubic
spline.
Also, spline allows you to obtain the interpolated yy values directly, so:
x = [3 4 7 9];
y = [2 1 2 0.5];
xx = 0:0.1:10;
yy = spline(x,[0 y 0], xx);
plot(xx,yy)
This gives me the plot below.
Looking at this, the slope is zero at the boundaries (x=3 and x=9), which is what we are asking of a 'clamped' spline with zero gradient at the boundaries. If you wish to have zero gradient beyond the boundaries, I would recommend just doing the following:
yy(xx<x(1)) = y(1);
yy(xx>x(length(x))) = y(length(y));
Giving:
Edit
This gives a continuous y function at the end-knots, but y' is not smooth at the end-knots. If you would like y' to be smooth you could pad your input arrays and use this as the input to your spline function. This would give you some oscillations as shown below, though, which may or may not be what you want.
% Spline on padded input arrays
x = [0 1 2 3 4 7 9 10 11 12];
y = [2 2 2 2 1 2 0.5 0.5 0.5 0.5];
yy = spline(x,y, xx);
I have my data in 2 vectors.
Then using [n,xout] = hist(x,y) returns vectors n and xout containing the frequency counts and the bin locations.
x are my real data and y the segment where I want to build my histogram.
Than I use bar(xout,n) to plot the histogram. In the end I am fitting this histogram with gaussian fit.
Now I would like to know where my real data (each point of vector x), are located in the histogram?
Can someone help me to figure out them?
[~, result] = min(abs(bsxfun(#minus, y(:), x(:).')));
This gives, for each value in x, the index of the closest element in y. So y(result) is the closest element in y for each x.
Example:
>> x = [0.4 1.6 5.3 4.2 3.1 7.8];
>> y = [0 2 4 6 8];
>> [~, result] = min(abs(bsxfun(#minus, y(:), x(:).')))
result =
1 2 4 3 3 5
>> y(result)
ans =
0 2 6 4 4 8
You want to use histc.
[binCounts, idx] = histc(x, y);
Then to find the bin in which a certain value of x is:
bin = idx(x == 0.4);
Just watch out since histc second input is not the centers like hist, but the end value of each bin. So you might need to change your y vector.
Is it possible to use the size (s) of the points to 'weight' the line of best fit?
x = [1 2 3 4 5];
y = [2 4 5 3 4];
s = [10 15 20 2 5];
scatter(x,y,s)
hold on
weight = s;
p = polyfit(x,y,1); %how do I take into account the size of the points?
f = polyval(p,x);
plot(x,f,'-r')
Using Marcin's suggestion, you can incorporate lscov into polyfit. As the documentation explains, polynomial fitting is done by computing the Vandermonde matrix V of x, and then executing p = V\y. This is the standard formalism of any least-squares solution, and lends itself to weighted-least-squares in MATLAB through lscov.
Taking your x, y and weight vectors, instead of calling polyfit(x,y,n) you can do the following:
% Construct Vandermonde matrix. This code is taken from polyfit.m
V(:,n+1) = ones(length(x),1,class(x));
for j = n:-1:1
V(:,j) = x.*V(:,j+1);
end
% Solve using weighted-least-squares
p = lscov(V,y,weight);
You can even go one step further, and modify polyfit.m itself to include this functionality, or add another function polyfitw.m if you are not inclined to modify original MATLAB functions. Note however that polyfit has some more optional outputs for structure, computed using QR decomposition as detailed in the documentation. Generalization of these outputs to the weighted case will require some more work.
x = (1:10)';
y = (3 * x + 5) + rand(length(x),1)*5;
w = ones(1,length(y));
A = [x ones(length(x),1)];
p = lscov(A,y,w);
plot(x,y,'.');
hold on
plot(x,p(1)*x + p(2),'-r');
I have two arrays - X points and Y points. X array have some spaces (e. g. [0 1 2 6 7 8]), and Y array contains only values for that Xes. I've got that array as a local maxima from wavelet transform. I can plot it with plot(X,Y)
Now I want to get Y's on linspace - Y must contain values for any X from 0 to 8. I want to have the same plot plot(Y) as the previous plot(X, Y).
How can I do this?
It looks like you want to perform interpolation
xPts = [0 1 2 6 7 8];
yPts = ...
xPlot = 0:1:8;
yPlot = interp1(xPts,yPts,xPlot,'cubic')
plot(xPlot,yPlot)
Check the documentation for interp1 for the different interpolation schemes.
If there are repeated x-values, you can average the corresponding y-values
xPtsRep = [0 0 1 2 6 7 7 8]
yPtsRep = ...
[xPts,~,xIdx] = unique(xPtsRep);
yPts = accumarray(xIdx,yPtsRep,[],#mean);