Curve fitting in the command window#
Every name on this page is typed at the command window (or passed to
ICoreBlocks --console "<line>") and answers what MATLAB's Curve Fitting
Toolbox answers for the same arguments — the same model library, the same
defaults, the same start points, and the same refusals. If you have MATLAB code
that fits a curve and reads its coefficients and bounds, it should paste in and
run.
That promise is harder here than for most toolboxes, and it is worth saying why before the first example.
The start point is the answer, not a detail#
A nonlinear fit finds a local minimum. Two programs that agree exactly on the model and disagree on where the iteration started will answer different numbers, and both of them are correct least-squares fits — just of different basins. So matching MATLAB here means matching the rule it uses to pick the start, not only the criterion it then minimises.
Those rules are transcribed for every library model that has one: the
geometric-sequence estimate behind exp1, the four-partition quadratic behind
exp2, the mean-of-logs behind power1 and power2, the peel-a-peak loop
behind gauss1…gauss8, and the FFT peak searches behind sin1…sin8 and
fourier1…fourier8.
⚠ Three families have no rule at all — in MATLAB either. rat11…rat55,
weibull, and any fittype you write yourself have no start-point routine, so
MATLAB draws a random one and warns you that it did. Here they start at
1 for every coefficient, deterministically, so the same line answers the
same numbers twice. Give a "StartPoint" when the answer matters:
fit(x, y, ft, "StartPoint", [1, 1])
Your first fit#
>>> x = transpose([0,1,2,3,4,5,6,7,8,9,10]); y = transpose([0.2,1.1,1.9,3.2,3.9,5.1,5.8,7.2,8.1,8.9,10.2]); f = fit(x, y, "poly1")
f =
Linear model Poly1:
f(x) = p1*x + p2
Coefficients (with 95% confidence bounds):
p1 = 0.9973 (0.9631, 1.031)
p2 = 0.06818 (-0.1342, 0.2706) # Curve Fit
That display is MATLAB's, line for line. The value itself is then read the way
MATLAB reads a cfit:
>>> coeffvalues(f)
[[0.997273, 0.0681818]] # Matrix of Double
>>> f(2.5)
2.56136 # Double
>>> f.p1()
0.997273 # Double
⚠ f.p1 needs its parentheses here. Every member on this console is a
call — f.p1(), not f.p1 — which is the same rule a model's .Ts() follows.
The value is the same one MATLAB's f.p1 gives.
⚠ x and y must be COLUMNS, and the error says so in MATLAB's own words
(X must be a matrix with one or two columns.). That is what
prepareCurveData is for: it column-ises, drops non-finite points pairwise
so the two vectors stay aligned, and hands back what fit wants.
(prepareSurfaceData does the same for a surface's three vectors, expanding
two header vectors and a table of values the way meshgrid would.)
The model library#
| Family | Names | Fitted by |
|---|---|---|
| Polynomial | poly1 … poly9 | one least-squares solve — exact |
| Exponential | exp1, exp2 | iteration from MATLAB's own start |
| Power | power1, power2 | iteration from MATLAB's own start |
| Rational | rat11 … rat55 | iteration from a start of ones (see above) |
| Fourier | fourier1 … fourier8 | iteration from the FFT peak |
| Gaussian | gauss1 … gauss8 | iteration, one peak at a time |
| Sum of sines | sin1 … sin8 | iteration from the FFT peaks |
| Weibull | weibull | iteration from a start of ones (see above) |
| Smoothing | smooth(y, span, method) | six local fits, no model object |
Write your own with fittype:
>>> ft = fittype("a*exp(-b*x)")
ft =
General model:
ft(a,b,x) = a*exp(-b*x) # Curve Fit Model
⚠ The coefficients are the free names in ALPHABETICAL order, which is
MATLAB's rule and the trap for a "StartPoint" vector written in the order you
typed the terms:
>>> coeffnames(fittype("c*x^2 + a*x + b"))
'a b c' # String
So "StartPoint", [1, 2, 3] sets a = 1, b = 2, c = 3 — not the order the
expression reads in.
Bounds, derivatives and integrals#
>>> confint(f)
[[0.963059, -0.134227], [1.03149, 0.270591]] # Matrix of Double
>>> predint(f, [1; 5])
[[0.666462, 1.46445], [4.67976, 5.42933]] # Matrix of Double
>>> differentiate(f, [1; 5])
[[0.997273], [0.997273]] # Matrix of Double
>>> integrate(f, [1; 5], 0)
[[0.566818], [12.8068]] # Matrix of Double
confint answers 2 rows by one column per coefficient — lower row then upper —
and takes a level: confint(f, 0.9). predint answers one row per point and
takes all four of MATLAB's interval kinds:
predint(f, xq, level, "observation" | "functional", "on" | "off")
"observation" (the default) predicts a new measurement, "functional"
the fitted curve; "on" makes the band simultaneous over every point.
⚠ confint and predint refuse an interpolant and a spline, on both
sides — there are no fitted coefficients to bound. And a coefficient you pinned
at a bound comes back NaN, because it was not estimated.
differentiate is exact for every library model — each carries its own
derivative — and answers a second output too:
[d1, d2] = differentiate(f, xq). integrate(f, xq, x0) integrates from x0
to each query point, in closed form where the model has one.
Options: fitoptions#
An option set is a string carrying its own call, the same shape odeset
and optimoptions use on this console:
>>> fitoptions("Method", "NonlinearLeastSquares", "StartPoint", [1, 1])
'fitoptions('StartPoint',[1 1],'Method','NonlinearLeastSquares')' # String
>>> fitoptions(fittype("poly2"))
'fitoptions('Method','LinearLeastSquares')' # String
You can pass the set to fit, or pass the same names directly on the call —
fit(x, y, "poly2", "Normalize", "on") is the shorter spelling of the same
thing.
⚠ The property list is per METHOD, and a name off it is refused rather than
ignored. "StartPoint" really is not a LinearLeastSquares property, and a
linear fit that accepted one and then dropped it would be the one answer that
cannot be right. The message names the method, as MATLAB's does.
Outliers: three different answers to the same data#
Move one point of that straight line a long way off it and the plain fit follows it:
>>> coeffvalues(fit(x, yr, "poly1"))
[[0.923636, 1.17273]] # Matrix of Double
"Robust" discounts it, "Exclude" drops it, and a weight would pull the line
towards it:
>>> coeffvalues(fit(x, yr, "poly1", "Robust", "Bisquare"))
[[0.995658, 0.0957771]] # Matrix of Double
>>> coeffvalues(fit(x, yr, "poly1", "Exclude", m))
[[0.995684, 0.092011]] # Matrix of Double
⚠ "Exclude" takes the points to LEAVE OUT — true means excluded, which
is the opposite of what the name suggests to most people. excludedata builds
that mask for you from a range, a domain, a box or a list of indices.
⚠ "Robust" is "Bisquare" or "LAR", and they are genuinely different
weightings, not two names for one. Both match MATLAB, including the details
that are MATLAB's own rather than the textbook's — the leverage adjustment on
each residual, the scale estimate taken after discarding the smallest
residuals, and the floor under that scale that stops a near-perfect fit from
calling every point an outlier.
Normalising#
>>> coeffvalues(fit(x, y, "poly2", "Normalize", "on"))
[[0.0679487, 3.30758, 4.99277]] # Matrix of Double
"Normalize", "on" centres and scales x before fitting, which is worth doing
for a high-order polynomial over a wide range. The coefficients are then of a
different polynomial — the curve is the same, the numbers are not — and the
display says which two numbers were used, so a fit always tells you whether it
was normalised.
Interpolants and the smoothing spline#
Five of the model names do not fit coefficients at all — they build a piecewise polynomial through (or near) your points:
| Name | What it is |
|---|---|
"cubicinterp" | the not-a-knot cubic — the same curve spline(x, y, xq) gives |
"pchipinterp" | the shape-preserving cubic — the same curve pchip gives |
"linearinterp" | straight lines between the points, extrapolating past the ends |
"nearestinterp" | the nearest sample, clamping past the ends; a tie takes the sample above |
"smoothingspline" | the cubic smoothing spline, with "SmoothingParam" between 0 and 1 |
⚠ The two cheap interpolants differ only at the ends, and it is worth
knowing which way round: "linearinterp" continues the end segment, and
"nearestinterp" holds the end value. Inside the data they agree with what
their names say.
"smoothingspline" with no "SmoothingParam" picks one by MATLAB's own rule —
p = 1 interpolates, p = 0 is the least-squares straight line, and the
automatic choice sits where the two trade off (0.9 on an evenly spaced grid).
csaps is the same curve under its own name, and [f, p] = csaps(x, y) hands
back the parameter it chose:
csaps(x, y) the fit, with the automatic p
csaps(x, y, p) the fit, with your p
csaps(x, y, p, xq) the smoothed VALUES at xq
⚠ These five have no coefficients, so coeffvalues, confint and
predint all refuse them by name — as MATLAB's do. differentiate and
integrate still work and are exact, because the derivative of a piecewise
polynomial is another one.
The spline forms: pp and B-#
Everything above hands you a fit — a model with named coefficients. The spline half of the toolbox hands you a form instead: the piecewise polynomial itself, as its breaks and its coefficients, which you can then evaluate, differentiate, integrate or convert.
A form is a struct on both sides, with MATLAB's own field names:
> x = [0, 1, 2, 3, 4, 5]; y = [0, 0.8, 0.9, 0.1, -0.8, -1]; p = csapi(x, y)
p =
breaks: [1×6 double]
coefs: [5×4 double]
pieces: 5
order: 4
dim: 1 # Struct
Read a field with p.breaks, p.coefs, p.pieces or p.order, and evaluate
with fnval:
> fnval(p, [0.5, 2.5, 4.5])
[[0.458333, 0.575, -1.03333]] # Matrix of Double
⚠ p.form refuses. It is the character string 'pp' in MATLAB, and a
struct here holds numbers only — so the refusal says which rule it met and how
to tell the two forms apart: a form with breaks is a pp, and one with
knots is a B-form.
Building one#
| Call | What you get |
|---|---|
csapi(x, y) | the not-a-knot cubic interpolant — the same curve as spline |
csapi(x, y, xx) | its values at xx, which is what csapi.m itself does |
csape(x, y) | the cubic interpolant with estimated end slopes |
csape(x, y, conds) | "complete"/"clamped", "not-a-knot", "periodic", "second", "variational", or the pair [left right] of 0, 1, 2 |
csape(x, y, conds, valconds) | the same, with the two end values you name |
spap2(knots, k, x, y) | the least-squares spline of order k |
spap2(pieces, k, x, y) | the same, with the knots chosen for you |
spaps(x, y, tol) | the smoothing spline that keeps the error at tol |
ppmak(breaks, coefs, 1) | a pp you built yourself |
spmak(knots, coefs) | a B-form you built yourself |
⚠ csape(x, y) does not clamp the end slopes to zero. With no end values
it estimates each one by local interpolation through the first three or four
points — so csape(x, y) and csape(x, y, "complete", [0, 0]) are different
curves, and the difference is visible in the first coefficient row. This is
MATLAB's behaviour, transcribed; it surprises people in both consoles equally.
⚠ csape reads the first letter of the condition word and nothing else, so
"clamped" and "complete" are one condition, and so are "second" and
anything else beginning with s.
The smoothing spline by tolerance#
spaps is csaps's dual: csaps takes the smoothing parameter and spaps
takes the error you are willing to accept, then finds the parameter that
delivers it.
> [sp, values, rho] = spaps(x, y, 0.05)
sp =
knots: [1×12 double]
coefs: [1×8 double]
number: 8
order: 4
dim: 1 # Struct
values = [[0.146016, 0.7527, 0.786114, 0.117646, -0.669475, -1.11999]] # Matrix of Double
rho = 8.80269 # Double
values is the smoothed data at your own sites, and rho is what the search
settled on. Three things about it are worth knowing before you compare numbers
with anyone:
- The default weights are the trapezoidal rule, not ones. The first and
last samples carry half the weight of an interior one. Pass
wyourself if that is not what you want — and expect different coefficients when you do. - A negative
tolnamesrhodirectly and skips the search entirely. m— the fourth-and-fifth-argument formspaps(x, y, tol, w, m)— is the derivative whose size is being penalised:1gives a piecewise linear answer,2(the default) the usual cubic smoothing spline,3a quintic.
Reading, differentiating, converting#
fnval evaluates a form, fnder differentiates it, fnint integrates it, and
fn2fm converts between the two forms:
| Call | What it does |
|---|---|
fnval(f, xx) | evaluate — the two arguments work in either order, as in MATLAB |
fnder(f) | differentiate once; fnder(f, n) differentiates n times |
fnder(f, -1) | a negative order integrates, and answers exactly what fnint does |
fnint(f) | integrate; fnint(f, value) starts the integral from value |
fn2fm(f, "B-") | the same spline in B-form |
fn2fm(f, "pp") | and back |
fn2fm(f, "B-", sconds) | the same conversion with the smoothness at each interior break named, rather than guessed |
Each of them answers a form of its own, so fnder(csapi(x, y)) is a pp one
order lower and fnint(spaps(x, y, tol)) is a B-form one order higher.
⚠ Converting a pp to a B-form drops the breaks the spline is smooth
across, which is not a bug and not an optimisation — it is what the B-form
means. The not-a-knot cubic through six points is three times differentiable at
the second and second-to-last break, so those two breaks need no knot at all:
> b = fn2fm(csapi(x, y), "B-"); b.knots
[[0, 0, 0, 0, 2, 3, 5, 5, 5, 5]] # Matrix of Double
Six breaks in, four distinct knots out. Round-tripping back through
fn2fm(…, "pp") gives you the same curve to the last digit.
The third argument is how you take that decision yourself, and it is two
arguments in one — MATLAB's own rule: a single number strictly between 0 and
1 is the tolerance the guess uses, and anything else is the list of
smoothness counts, one per interior break. fn2fm(pp, "B-", [3 3 3 3]) keeps
every interior break as a single knot; [0 0 0 0] gives each of them four,
which is the same curve written as one Bézier piece per interval.
What the spline forms will not do#
| MATLAB | Here |
|---|---|
f.form | Refused by name: 'pp' and 'B-' are character strings, and a struct here holds numbers. The fields tell the forms apart |
fn2fm(f, "BB") | Refused: the Bernstein-Bézier form's struct would carry the same fields as a B-form's, so the two would be indistinguishable once built. "B-" is the same spline |
fnplt(f) | Refused: it draws. plot(xx, fnval(f, xx)) is the same picture |
ppmak(breaks, coefs) with a matrix | Refused: MATLAB reads the rows as a vector-valued spline, which has no type here. ppmak(breaks, coefs, 1) reads the same matrix as pieces-by-order |
csapi({x1, x2}, y), spap2({…}, …) | Refused: gridded, tensor-product splines need a cell array, which this console has no type for |
spaps(x, y, 0, w, 3) | Refused: the quintic variational interpolant is the one call MATLAB answers through spapi and fncmb. Every other spaps call answers |
How good is the fit?#
[f, gof] = fit(x, y, "poly2")
gof carries MATLAB's five fields, in MATLAB's order: sse, rsquare,
dfe, adjrsquare, rmse. Read one with gof.rsquare() — every member on
this console is a call.
⚠ gof.rmse is sqrt(sse/dfe), not sqrt(sse/n). Base MATLAB's own
rmse(y, yfit) divides by n, so the two give different numbers for the same
fit and neither is wrong — one is the residual standard error and the other is
the root mean square. If you are comparing against a number from elsewhere,
check which one it is.
⚠ An interpolant's gof is sse 0, rsquare 1, dfe 0, and NaN for the
last two — it passes through every point, so there is no error to report and
nothing to divide by. That is an answer, not a gap.
What a fit will tell you about itself#
>>> type(f)
'poly1' # String
>>> category(f)
'library' # String
>>> class(f)
'cfit' # String
>>> class(fittype("poly1"))
'fittype' # String
type answers the model's own name, category says which of the four kinds
of model it is (library, custom, interpolant or spline), and
coeffvalues, formula, numcoeffs, indepnames, dependnames and
argnames answer as MATLAB's do. ⚠ Four of them answer a single string here where MATLAB answers
a cell array — coeffnames, indepnames, dependnames and argnames —
with the names separated by single spaces, because this console has one string
type and no cell type. Split on spaces where MATLAB code indexes a cell.
Drawing a fit#
plot means something different when its first argument is a fit. It is the
same overload MATLAB has — one verb, told apart by the kind of what you hand
it — so nothing about plot(x, y) changes:
plot(f)
plot(f, "r--")
plot(f, x, y [, outliers] [, type | {type, ...}] [, level] )
plot(f) on its own draws the fitted curve at 1001 points over the range of
the data the fit was made from. The fit remembers that range: it is the data
that survived "Exclude", in the coordinates you gave, and "Normalize" does
not move it. Pass x and y as well and the data is drawn as markers with the
curve over it, and the drawing range becomes the range of the x you passed —
so a subset of the data draws over the subset.
| Type | What it draws |
|---|---|
"fit" (the default) | the data, then the curve |
"residuals" | y - f(x) at each data point, and a zero line |
"predobs" | the data, the curve, and predint's observation band |
"predfunc" | the same with its functional band — the narrower pair |
"deriv1", "deriv2" | the first or second derivative alone: no data markers, even when you passed data |
"integral" | the integral from the range's own start, alone |
A braced list draws one axes per type — plot(f, x, y, {"fit", "residuals"})
puts the fit in the chart and the residuals in a subchart under it.
⚠ That braced form is the one shape of this verb that will not cross to
MATLAB, in either direction: MATLAB's several-types argument is a cell array
and this console has no cell type, so toMatlab and fromMatlab both refuse
it by name. Everything else here crosses argument for argument. Write it as
two calls with hold on between them if the script has to survive the trip.
Every line carries MATLAB's own legend name — Data, Excluded data,
Fitted curve, Zero line, Prediction bounds - 95%, First derivative,
Second derivative, Integral from 0 to x — and c.path(i).info reads them
back:
>>> x = [1; 2; 3; 4; 5]; y = [2.1; 3.9; 6.2; 7.8; 10.1]; f = fit(x, y, "poly1"); c = plot(f, x, y, "predobs")
plot(poly1): 4 line(s) — Data, Fitted curve, Prediction bounds - 95%
>>> p = c.path(1); p.info
p: name='Fitted curve', points=1001, bounds=[x: 1..5, y: 2.04..10]
>>> p = c.path(2); p.info
p: name='Prediction bounds - 95%', points=1001, bounds=[x: 1..5, y: 1.27976..9.23976]
⚠ Four lines, three names. The upper prediction bound is deliberately unnamed, on both sides — MATLAB's legend carries one entry for the pair, not two, and this console draws it the same way.
⚠ The confidence level goes after the type, and a line spec goes before
it. plot(f, x, y, "predobs", 0.9) is the way to widen or narrow the band;
plot(f, x, y, "r--", "predobs") is the way to style it. The other order —
plot(f, x, y, "predobs", "r--") — is refused, because the slot after a type
is the level and nothing else. That reads backwards, and it is MATLAB's rule
rather than a restriction added here.
⚠ A line spec styles the DATA, not the curve. With x and y in the
call, plot(f, x, y, "r--") makes the data red and dashed and leaves the
fitted curve alone. With no data there is no data line, so plot(f, "r--")
styles the curve. Measured on both sides.
⚠ "stem" is not a plot type, whatever older documentation says. R2026a
answers Invalid color, marker, or line style for it — it falls through to the
line-spec reader, exactly as a misspelling does — and so does this console.
The fourth positional argument, before any type, is the outliers vector
that excludedata answers (or a list of 1-based indices). Those points leave
the Data line and get an Excluded data line of their own:
>>> o = excludedata(x, y, "indices", 2); c = plot(f, x, y, o); p = c.path(1); p.info
p: name='Excluded data', points=1, bounds=[x: 2..2, y: 3.9..3.9]
⚠ predobs and predfunc refuse an interpolant and a spline, as MATLAB
does — there are no coefficients to put a band on — but deriv1, deriv2 and
integral work for them, because a piecewise polynomial has derivatives.
Surfaces#
A fit can have two independent variables. Pass x and y as the two columns
of one matrix and ask for one of MATLAB's surface polynomials:
>>> x = [0; 1; 2; 3; 0; 1; 2; 3; 0; 1; 2; 3]; y = [0; 0; 0; 0; 1; 1; 1; 1; 2; 2; 2; 2]; z = 1 + 2*x + 3*y + 0.5*x.*y + 0.1*x.^2; sf = fit([x y], z, "poly21")
sf =
Linear model Poly21:
sf(x,y) = p00 + p10*x + p01*y + p20*x^2 + p11*x*y
Coefficients (with 95% confidence bounds):
p00 = 1 (1, 1)
p10 = 2 (2, 2)
p01 = 3 (3, 3)
p20 = 0.1 (0.1, 0.1)
p11 = 0.5 (0.5, 0.5) # Curve Fit
poly00 through poly55: the first digit is the degree in x and the second
the degree in y.
The transcripts further down this section run on a thirteen-point scattered fixture rather than on this grid, because several of the settings they show answer the same number on a regular grid:
>>> sx = [0.82; 0.98; 0.73; 0.34; 0.58; 0.11; 0.91; 0.88; 0.82; 0.26; 0.59; 0.023; 0.43]; sy = [0.31; 0.16; 0.18; 0.42; 0.094; 0.6; 0.47; 0.7; 0.7; 0.64; 0.034; 0.069; 0.32]; sz = [0.6; 0.2; 0.48; 0.6; 0.39; 0.4; 0.32; 0.098; 0.12; 0.32; 0.36; 0.83; 0.61];
⚠ The terms are not the whole grid. polyIJ carries x^i*y^j when
i <= I and j <= J and i + j <= max(I, J), so poly12 has no
p20 and poly23 has neither p30 nor p22. The coefficients come out by
total degree, and by descending power of x within each degree — p00,
p10, p01, p20, p11, p02, … Both rules are MATLAB's, and
coeffnames(fittype("poly23")) will tell you the answer for any of them.
Evaluate it any of the three ways MATLAB does — a scalar broadcasts against a vector:
>>> sf(2, 1)
9.4 # Double
>>> sf([2, 1])
9.4 # Double
>>> sf([1; 2], [1; 1])
[[6.6], [9.4]] # Matrix of Double
[sf, gof] = fit(...), confint, predint and differentiate all read as
they do for a curve, because a surface polynomial is a linear model and
reaches them through the same solve.
⚠ differentiate means something else here, and it is MATLAB's naming:
on a curve [d1, d2] are the first and second derivative, on a surface
they are the two partial first derivatives, df/dx and df/dy. With one
output you get df/dx on both.
⚠ predint takes only the matrix form — predint(sf, [x y]). Its third
argument is the confidence level, so there is no room for a second coordinate
vector; MATLAB answers XY must have two columns. and so does this.
⚠ integrate does not exist for a surface on either side.
"Normalize" works, and it centres and scales each variable by its own
mean and deviation — which is why the display writes two lines for it:
>>> sf = fit([sx sy], sz, "lowess", "Normalize", "on")
sf =
Locally weighted smoothing linear regression:
sf(x,y) = lowess (linear) smoothing regression computed from p
where x is normalized by mean 0.5748 and std 0.3175
and where y is normalized by mean 0.3613 and std 0.2451
Coefficients:
p = coefficient structure # Curve Fit
The local regressions#
"lowess" fits a local plane and "loess" a local quadratic, weighted by
distance with the tricube:
>>> sf = fit([sx sy], sz, "lowess")
sf =
Locally weighted smoothing linear regression:
sf(x,y) = lowess (linear) smoothing regression computed from p
Coefficients:
p = coefficient structure # Curve Fit
>>> sf = fit([sx sy], sz, "loess"); sf([0.5, 0.5; 0.2, 0.3])
[[0.555346], [0.750278]] # Matrix of Double
>>> sf = fit([sx sy], sz, "lowess", "Span", 0.5, "Robust", "Bisquare"); sf([0.5, 0.5; 0.2, 0.3])
[[0.416714], [0.555369]] # Matrix of Double
"Span" is the proportion of the points each local regression sees, and it
has a floor: the neighbour count is max(ceil(span*n), t + 2) with t the
number of terms in the local polynomial, so a lowess never runs on fewer than
5 points and a loess never on fewer than 8. On thirteen points "Span", 0 and
the default 0.25 are therefore the same fit.
⚠ A lowess goodness record has a fractional dfe, because a local
regression has no integer parameter count. It is n - 2*(1 + n/k) with k the
neighbour count, and rmse and adjrsquare divide by that:
>>> [sf, g] = fit([sx sy], sz, "lowess"); [g.sse, g.dfe, g.rmse]
[[0.0152969, 5.8, 0.0513556]] # Matrix of Double
The two surface interpolants#
"thinplateinterp" and "biharmonicinterp" each pass through every point.
Both are one dense linear solve over the raw scattered points — no
triangulation, which is why these two are here and the other three are not:
>>> sf = fit([sx sy], sz, "thinplateinterp")
sf =
Thin-plate spline interpolant:
sf(x,y) = thin-plate spline computed from p
with thin-plate extrapolation
Coefficients:
p = coefficient structure # Curve Fit
>>> sf = fit([sx sy], sz, "biharmonicinterp"); sf(0.5, 0.5)
0.494691 # Double
Neither has coefficients to read or to bound, so coeffvalues, confint and
predint are refused for them and for the local regressions:
>>> sf = fit([sx sy], sz, "lowess"); coeffvalues(sf)
error: coeffvalues(f): a lowess fit has no coefficients -- MATLAB answers an OBJECT (curvefit.LowessFit, a SurfaceInterpolant) here and this console has no kind for one (evaluate it with f(x) instead)
⚠ All four are surface-only in MATLAB too. fit(x, y, "lowess") is an
error there, not a curve fit — getfittypes.m allows these four two
independent variables and not one — so this console answers MATLAB's own
sentence rather than inventing a curve:
>>> fit(sx, sz, "lowess")
error: The library function 'Locally weighted smoothing linear regression' is for fitting surfaces and requires two independent variables, that is, X must have two columns.
The curve half of the same local regression is smooth(y, "lowess") — see
The model library above — and it answers numbers rather than a fit.
A model of your own, in two variables#
Name both independents after "independent". MATLAB spells the pair as the
cell {'x', 'y'}; this console has no cell type, so the two names are written
out and the list ends at the next option word:
>>> ft = fittype("a*exp(b*x) + c*y", "independent", "x", "y", "dependent", "z"); cf = fit([sx sy], sz, ft, "StartPoint", [1, 1, 1])
cf =
General model:
cf(x,y) = a*exp(b*x) + c*y
Coefficients (with 95% confidence bounds):
a = 0.8094 (0.5578, 1.061)
b = -0.6724 (-1.201, -0.1433)
c = -0.4205 (-0.8002, -0.04092) # Curve Fit
The coefficient rule is the same one a curve follows, with two names removed
instead of one: every free name that is not an independent variable, in
alphabetical order. fittype("terms", "x", "y", "1", "independent", "x", "y")
is the linear form.
⚠ fittype("a*x + b*y") with no "independent" is an error on both sides,
and MATLAB's sentence says why: y is the default dependent name, so the
expression's y would have to be a coefficient as well, and it cannot be both.
What a surface will not do yet#
| MATLAB | Here |
|---|---|
fit([x y], z, "linearinterp" | "cubicinterp" | "nearestinterp") | Refused by name: scattered-data interpolation needs a Delaunay triangulation this console has nothing to hold |
plot(sf) | Refused by name: it draws a surface, and this console's chart draws in two dimensions — the same reason surf, mesh and plot3 are refused. Evaluate a slice and plot that: plot(xq, sf(xq, y0)) |
What is not here yet#
| MATLAB | Here |
|---|---|
[f, gof, output] = fit(…) — the third and fourth outputs | Refused by name: they are the solver record and its convergence MESSAGE, and a message is a character field this console's record cannot hold |
coeffvalues of an interpolant | Refused by name: MATLAB answers a pp struct there — an object for the surface interpolants and the local regressions — and this console has no type for either. Evaluate it with f(xq) |
cftool, sftool | Never — they are click-driven apps. fit is what they drive |
fittype({'x', '1'}), 'coefficients', {'a','b'} and 'independent', {'x','y'} | Spelled fittype("terms", "x", "1"), fittype(…, "coefficients", "a", "b") and fittype(…, "independent", "x", "y") — this console has no cell type |
Every transcript on this page is real output, captured with
ICoreBlocks --console "<line>" on 2026-09-05 from a build of commit
3c8ea988 — the local-regression, surface-interpolant and two-variable
fittype transcripts from the build that landed those the same day — and the interpolant, spline and goodness transcripts from the
build that landed those the same night. The spline-form transcripts were
captured on 2026-09-05 from the build that landed csapi, csape, spap2,
spaps and their readers, and every number in them was diffed against R2026a
with the Curve Fitting Toolbox installed before it was written down — the worst
disagreement over the whole section was 5.3e-15. The drawing transcripts were captured
the same day from the build that landed plot(f, ...), and every claim on
this page about what MATLAB draws was read off MATLAB's own handles on R2026a
before it was written down.