9
$\begingroup$

I have some data. I want to find the linear regression of the data, but force it to include a certain point. (This question is a corollary to this old question; in that question, the user wished to force the fit to include the origin $(0, 0)$.)

Here is my data, and I want the linear regression to go through the point $(0, 100)$.

data = {{0, 100}, {4, 86}, {6, 80}, {7, 73}, {8, 66}, {13, 49}, {14, 44}, 
{15, 41}, {16, 33}, {17, 28}};

I know that I can fit the data to a line using

Fit[data, {1, x}, x]

or

Normal[LinearModelFit[data, x, x]]

which both yield 102.167 - 4.21667 x, which of course does not go through $(0,100)$.

fit = Fit[data, {1, x}, x];
Show[{
  ListPlot[data, PlotRange -> All, Joined -> True, Mesh -> Full, 
   Frame -> True],
  Plot[fit, {x, 0, 25}, PlotRange -> All, PlotStyle -> Red]
  }, AxesOrigin -> {0, 0}]

fitting

I thought that perhaps Fit[data, {100, x}, x] would force Fit to use a y-intercept of 100, but instead Fit simply scales the constant term, yielding the same result:

102.167 - 4.21667 x
$\endgroup$
5
  • 1
    $\begingroup$ The fixedLine[] routine in my answer should be able to do this. Anyway: pt = {100, 0}; \[FormalA] \[FormalX] + {-\[FormalA], 1}.pt /. FindFit[TranslationTransform[-pt][data], \[FormalA] \[FormalX], \[FormalA], \[FormalX]] $\endgroup$ Mar 10, 2018 at 22:38
  • 1
    $\begingroup$ Not Mathematica specific, but I've used bespoke regularization terms to do impose some pretty weird priors on linear regression models. You could, for example, penalize models which don't pass through the point you want very heavily. An even weirder thing I've done is penalize models in which the coefficients themselves were nonlinear. (I.e. I had a prior that w1,w2,w3 should be ~1,.6,.3 and I wanted to force the regression to justify deviation from that prior.) If Mathematica has a way to allow for regularization (which I expect it does), then this would be a way to do what you want. $\endgroup$
    – Tyler
    Mar 11, 2018 at 0:10
  • $\begingroup$ Almost always when forcing a line through (0,0) there really isn't a data point or a measurement at (0,0). Is there really a measurement at (100,0) ? $\endgroup$
    – JimB
    Mar 11, 2018 at 2:13
  • 1
    $\begingroup$ if you're foring it to go though a point it's not a linear regression any more, it's just some sort of fit (possibly least-squares) for the slope of the line. $\endgroup$
    – Jasen
    Mar 11, 2018 at 4:00
  • 1
    $\begingroup$ Add a single observation dummy variable. $\endgroup$ Mar 11, 2018 at 4:07

5 Answers 5

10
$\begingroup$

tl;dr

Make your data go through $(0,0)$ by subtracting the required point and fit a line through the origin, then add back the required point:

datap = # - {0, 100} & /@ data;
Fit[ datap, {x}, x] + 100

Annoying maths:

You have to include the condition as an external constraint and the standard Fit[...] does not allow for that. A linear regression is simply trying to minimize the expression (assuming identical uncertainties $\sigma_k$ for ease of notation)

$$\sum_k (a x_k + b - y_k)^2 $$

wrt the parameters $a$ and $b$. Your external constraint has the form

$$x_0 a + b - y_0 = 0$$

with $x_0=0$ and $y_0 = 100$ in your special case. The constraint, being zero, can be added the above expression to minimize

$$\sum_k (a x_k + b - y_k)^2 - \lambda ( a x_0 + b - y_0 )$$

This addition will couple the (otherwise independent) derivatives wrt $a$ and $b$

$$\sum_k 2 x_k (a x_k + b - y_k) = \lambda x_0$$ $$\sum_k 2 (a x_k + b - y_k) = \lambda$$

which, together with the constraint, can be easily (and analytically!) solved for a and b. Use the constraint to express $b$

$$b = -a x_0 + y_0$$

which leads to

$$\sum_k 2 x_k (a (x_k-x_0) - (y_k-y_0) ) = \lambda x_0$$ $$\sum_k 2 (a (x_k-x_0) - (y_k-y_0) ) = \lambda$$

or

$$\sum_k 2 (x_k-x_0) (a (x_k-x_0) - (y_k-y_0) ) = 0$$

which is, somewhat unsurprisingly, exactly the equation you would have gotten if you had started with the dataset $x'_k = x_k-x_0, y'_k = y_k-y_0$ and the model $$y= a x$$

So you should get the required result using

datap = # - {0, 100} & /@ data;
Fit[ datap, {x}, x] + 100

which indeed results in

100 - 4.05 x
$\endgroup$
11
$\begingroup$

Use IncludeConstantBasis -> False to go through $(100,0)$:

Normal[LinearModelFit[data, x - 100, x, IncludeConstantBasis -> False]]
(*  -0.679766 (-100 + x)  *)

Or LinearOffsetFunction -> (100 &) for $(0, 100)$:

Normal[LinearModelFit[data, x, x, IncludeConstantBasis -> False, 
  LinearOffsetFunction -> (100 &)]]
(*  100 - 4.05 x  *)

Or both for an arbitary point:

SeedRandom[0];
pt = RandomChoice[data]  (* pick a data point *)
(*  {15, 41}  *)

Normal[LinearModelFit[data, x - pt[[1]], x, 
  IncludeConstantBasis -> False, LinearOffsetFunction -> (pt[[2]] &)]]
(*  41 - 4.02727 (-15 + x)  *)
$\endgroup$
9
$\begingroup$

Use NonlinearModelFit (everything that is linear is also nonlinear, you know...):

model = m x + b;
model = model /. Solve[m x + b == 0 /. x -> 100, b];
f = NonlinearModelFit[data, {model}, {m}, x]
f["BestFitParameters"]

{m -> -0.679766}

Show[
 ListLinePlot[data],
 Plot[f[x], {x, 0, 100}, PlotStyle -> ColorData[97][2]],
 PlotRange -> All
 ]

enter image description here

I suppose you meant $(0,100)$ as a point to go through but you only get what you ask for ;)

$\endgroup$
2
  • 4
    $\begingroup$ "You are technically correct - the best kind of correct" $\endgroup$
    – Chris K
    Mar 10, 2018 at 23:21
  • $\begingroup$ But you could be completely correct with adding something like {x0, y0} = {0, 100}; model = model /. Solve[(model /. x -> x0) == y0, b]. $\endgroup$
    – JimB
    Mar 11, 2018 at 2:10
5
$\begingroup$

Here's a hacky solution: use the Weights option in LinearModelFit to give the point at (0,100) a huge weight.

fit2 = Normal[LinearModelFit[data, x, x,
  Weights -> Table[If[i == 1, 10^20, 1], {i, Length[data]}]]]

(* 100. - 4.05 x *)

Show[{ListPlot[data, PlotRange -> All, Joined -> True, Mesh -> Full,
  Frame -> True], 
  Plot[fit2, {x, 0, 25}, PlotRange -> All, PlotStyle -> Red]},
  AxesOrigin -> {0, 0}]

Mathematica graphics

$\endgroup$
1
$\begingroup$

We can simply add any constraint in the second argument of NonlinearModelFit:

ClearAll[a, b, model]
model[x_] := a + b x;

Unconstrained model:

nlmf0 = Normal @ NonlinearModelFit[data, model[x], {a, b}, x]
102.167 - 4.21667 x

Force the fit to go through {0, 100}:

nlmf1 = Normal @ NonlinearModelFit[data, {model[x], model[0] == 100}, {a, b}, x]
100. - 4.05 x

Force the fit to go through {5, 75}:

nlmf2 = Normal @ NonlinearModelFit[data, {model[x], model[5] == 75}, {a, b}, x]
93.3182 - 3.66364 x

enter image description here

Show[Plot[{nlmf0, nlmf1, nlmf2}, {x, 0, 20}, 
  Epilog -> {Red, PointSize[Large], Point[{{0, 100}, {5, 75}}]}, 
  PlotLegends -> {nlmf0, nlmf1, nlmf2}], ListPlot[data]]
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.