Function Repository Resource:

MultiNonlinearModelFit

Source Notebook

Fit multiple datasets with multiple expressions that share parameters

Contributed by: Sjoerd Smit

ResourceFunction["MultiNonlinearModelFit"][{dat1,dat2,},{form1,form2,},{β1,},{x1,}]

is a generalized form of NonlinearModelFit where each dataset dati is fitted with expression formi. The expressions are allowed to share parameter values βi with each other.

ResourceFunction["MultiNonlinearModelFit"][{dat1,dat2, }, <|"Expressions"{form1,form2,}, "Constraints"cons |>, {β1,},{x1,}]

fits the model subject to parameter constraints cons.

Details and Options

ResourceFunction["MultiNonlinearModelFit"] works by recasting the data and fitting forms into a form suitable for NonlinearModelFit.
The datasets dati must all be numeric matrices.
The number of datasets dati must match the number of expressions formi. It is also possible to specify a function that evaluates to a list when provided with numerical arguments.
The parameters βi can be specified in the same way as in NonlinearModelFit. ResourceFunction["MultiNonlinearModelFit"] also inherits all options from NonlinearModelFit.
Internally, the datasets dati are joined together into a single matrix, so if a Weights vector is specified as an option, it should have the same length as Join[dat1,dat2,]. It is also possible to specify a Weights vector that has a length matching the number of datasets. There is a special option Weights "InverseLengthWeights" to specify that each dataset should weigh equally in the fit, regardless of the number of points in each. Finally, it is also possible to specify the Weights as a list of vectors matching the input data.
The returned model uses the symbol (\[FormalN]) to switch between the different expressions formi. This symbol should therefore be avoided by the user when using ResourceFunction["MultiNonlinearModelFit"]. This symbol can be changed with the "DatasetIndexSymbol" option.

Examples

Basic Examples (2) 

Fit some random data to simple linear models with a shared slope parameter:

In[1]:=
data = RandomVariate[BinormalDistribution[0.7], {2, 100}];
data[[1, All, 2]] += 1.;
data[[2, All, 2]] += -1.;

fit = ResourceFunction["MultiNonlinearModelFit"][
  data,
   {a x + b1, a x + b2}, {a, b1, b2}, {x}
  ]
Out[4]=
In[5]:=
Show[
 ListPlot[data],
 Plot[{fit[1, x], fit[2, x]}, {x, -5, 5}]
 ]
Out[5]=

Fit the same model with parameter constraints to allow for a small difference between the slopes of the lines (this works best by using a quadratic form for the difference between the slope parameters, since (a1-a2)2is differentiable whereas Abs[a1a2] is not):

In[6]:=
fit = ResourceFunction["MultiNonlinearModelFit"][
  data,
  <|
   "Expressions" -> {a1 x + b1, a2 x + b2},
   "Constraints" -> (a1 - a2)^2 < 0.1^2 && b1 > 0 && b2 < 0 |>,
   {a1, a2, b1, b2},
  {x}
  ]
Out[6]=
In[7]:=
Show[
 ListPlot[data],
 Plot[{fit[1, x], fit[2, x]}, {x, -5, 5}]
 ]
Out[7]=

Scope (2) 

It is possible to define a fitting function that evaluates to a list of values equal to the number of datasets when provided with numerical parameters:

The fit function does not evaluate symbolically, only numerically:

In[8]:=
fitfun[{a, b1, b2}, x]
Out[8]=
In[9]:=
fitfun[{1, -1, 1}, 2]
Out[9]=

Use this function to fit with:

In[10]:=
fit = ResourceFunction["MultiNonlinearModelFit"][
  data,
   fitfun[{a, b1, b2}, x],
  {a, b1, b2},
  {x}
  ]
Out[10]=
In[11]:=
Show[
 ListPlot[data],
 Plot[{fit[1, x], fit[2, x]}, {x, -5, 5}]
 ]
Out[11]=

Fit two Gaussian peaks with a shared location parameter:

In[12]:=
xvals = Range[-5, 5, 0.1];
gauss[x_] := Evaluate@PDF[NormalDistribution[], x];

With[{amp1 = 1.2, amp2 = 0.5, width1 = 1, width2 = 2, sharedOffset = 0.5, eps = 0.05},
  dat1 = Table[{x, amp1 gauss[(x - sharedOffset)/width1] + eps RandomVariate[NormalDistribution[]]}, {x, xvals}];
  dat2 = Table[{x, amp2 gauss[(x - sharedOffset)/width2] + eps RandomVariate[NormalDistribution[]]}, {x, xvals}]
  ];
plot = ListPlot[{dat1, dat2}]
Out[13]=

Fit with the models that were used to generate the data:

In[14]:=
fit = ResourceFunction["MultiNonlinearModelFit"][
  {dat1, dat2},
  {
   amp1 gauss[(x - sharedOffset)/width1],
   amp2 gauss[(x - sharedOffset)/width2]
   },
  {amp1, amp2, width1, width2, sharedOffset},
  {x}
  ]
Out[14]=
In[15]:=
fit["BestFitParameters"]
Out[15]=

Extract the fits as a list of expressions:

In[16]:=
fits = fit[#, x] & /@ {1, 2}
Out[16]=

Compare the fits to the data:

In[17]:=
Show[
 plot,
 Plot[fits, {x, -5, 5}]
 ]
Out[17]=

Options (6) 

Weights (5) 

The Weights option can be specified in a number of ways. First generate datasets with an unequal number of points and offset them slightly:

In[18]:=
gauss[x_] := Evaluate@PDF[NormalDistribution[], x];
With[{amp1 = 1.2, amp2 = 0.5, width1 = 1, width2 = 2, sharedOffset = 0.5, eps = 0.025},
  dat1 = Table[
    {x, amp1 gauss[(x - sharedOffset)/width1] + eps RandomVariate[NormalDistribution[]]},
    {x, -5, 5, 0.1}
    ];
  dat2 = Table[
    {x, amp2 gauss[(x - sharedOffset + 1)/width2] + eps RandomVariate[NormalDistribution[]]},
    {x, -5, 5, 0.2}
    ]
  ];
fitfuns = {
   amp1 gauss[(x - sharedOffset)/width1],
   amp2 gauss[(x - sharedOffset)/width2]
   };
fitParams = {amp1, amp2, width1, width2, sharedOffset};
variables = {x};
plot = ListPlot[{dat1, dat2}]
Out[19]=

Fit the data normally. In this case, each individual data point has equal weights in the fit, so the first dataset gets more weight overall since it has more points:

In[20]:=
fit1 = ResourceFunction["MultiNonlinearModelFit"][
   {dat1, dat2},
   fitfuns, fitParams, variables
   ];
fit1["BestFitParameters"]
Out[21]=
In[22]:=
Show[plot, Plot[{fit1[1, x], fit1[2, x]}, {x, -5, 5}]]
Out[22]=

Assign more weight to the second dataset:

In[23]:=
fit2 = ResourceFunction["MultiNonlinearModelFit"][
   {dat1, dat2},
   fitfuns, fitParams, variables,
   Weights -> {1, 20}
   ];
fit2["BestFitParameters"]
Out[24]=
In[25]:=
Show[plot, Plot[{fit2[1, x], fit2[2, x]}, {x, -5, 5}]]
Out[25]=

Assign weights inversely proportional to the number of points in the dataset. This asserts that each dataset is equally important:

In[26]:=
fit3 = ResourceFunction["MultiNonlinearModelFit"][
   {dat1, dat2},
   fitfuns, fitParams, variables,
   Weights -> "InverseLengthWeights"
   ];
fit3["BestFitParameters"]
Out[27]=
In[28]:=
Show[plot, Plot[{fit3[1, x], fit3[2, x]}, {x, -5, 5}]]
Out[28]=

To assign weights for each individual data point, you can pass a list of vectors matching the input data:

In[29]:=
fit4 = ResourceFunction["MultiNonlinearModelFit"][
   {dat1, dat2},
   fitfuns, fitParams, variables,
   Weights -> {
     RandomReal[{0, 1}, Length[dat1]],
     RandomReal[{0, 1}, Length[dat2]]
     }
   ];
fit4["BestFitParameters"]
Out[30]=
In[31]:=
Show[plot, Plot[{fit4[1, x], fit4[2, x]}, {x, -5, 5}]]
Out[31]=

DatasetIndexSymbol (1) 

Use a different symbol to index the datasets:

In[32]:=
fit = ResourceFunction["MultiNonlinearModelFit"][
   RandomVariate[BinormalDistribution[0.7], {2, 100}],
    {a x + b1, a x + b2}, {a, b1, b2}, {x}, "DatasetIndexSymbol" -> mySymbol];
Normal[fit]
Out[33]=
In[34]:=
Table[Normal[fit], {mySymbol, {1, 2}}]
Out[34]=

Publisher

Sjoerd Smit

Version History

  • 7.1.0 – 14 October 2022
  • 7.0.0 – 06 October 2020
  • 6.0.0 – 24 July 2020
  • 5.0.0 – 05 December 2019
  • 4.0.0 – 22 May 2019
  • 3.0.0 – 19 April 2019
  • 2.0.0 – 01 April 2019
  • 1.0.0 – 14 March 2019

License Information