(*:Mathematica Version: 3.0 *) (*:Package Version: 2.0 *) (*:Name: DataFit` *) (*:Title: Fitting Curves To Data *) (*:Author: Silvia Heubach *) (*:Keywords: polynomial fit, sine fit, exponential fit, logistic fit, first and second differences, unit ratios, comparison of data with fitted functions *) (*:Summary: This package can be used in conjunction with the palette DataFitP. It fits specific functions to data and provides tools to determine which function type should be fitted *) BeginPackage["DataFit`", "Statistics`DescriptiveStatistics`", "Statistics`LinearRegression`"] Needs["Statistics`NonlinearFit`"] PlotData::usage = "PlotData[list] plots the data given in list as pairs of input/output values (the first entry in list may consist of the names for the variables)." FitComp::usage = "FitComp[data,funcs] creates a table containing the input and output values as given in data, together with values as computed for the list of function(s) given in funcs (expressed with x as the independent variable). In addition, the differences between prediction and data are computed." PolyFitGraph::usage = "PolyFitGraph[list,n] fits a polynomial of degree n (linear n=1, quadratic n= 2, cubic n=3) to the data given in list as pairs of input/output values (the first entry in list may consist of the names for the variables). The data and the graph of the fitted function are displayed together." PolyFitFunc::usage = "PolyFitFunc[list,n] gives the fitted polynomial of degree n (linear n=1, quadratic n= 2, cubic n=3) to the data given in list as pairs of input/output values (the first entry in list may consist of the names for the variables)." ExpoFitGraph::usage = "ExpoFitGraph[list] fits an exponential function whose horizontal asymptote is at level 0 to the data given in list as pairs of input/output values (the first entry in list may consist of the names for the variables). The data and the graph of the fitted function are displayed together." ExpoFitFunc::usage = "ExpoFitFunc[list] gives the fitted exponential function whose horizontal asymptote is at level 0 to the data given in list as pairs of input/output values (the first entry in list may consist of the names for the variables)." SineFitGraph::usage = "SineFitGraph[list,period] fits a sine function of the form a*Sin(b x + c) + d to the data given in list as pairs of input/output values (the first entry in list may consist of the names for the variables). The period (>0) is estimated from the data. The data and the graph of the fitted function are displayed together." SineFitFunc::usage = "SineFitFunc[list,period] gives the fitted sine function of the form a*Sin(b x + c) + d to the data given in list as pairs of input/output values (the first entry in list may consist of the names for the variables). The period (>0) is estimated from the data." LogisticFitGraph::usage = "LogisticFitGraph[list] fits a logistic function to the data given in list as pairs of input/output values (the first entry in list may contain the names of the variables). The data and the graph of the fitted function are displayed together." LogisticFitFunc::usage = "LogisticFitFunc[list] gives the fitted logistic function to the data given in list as pairs of input/output values (the first entry in list may contain the names of the variables)." FirstUnitDiff::usage = "FirstUnitDiff[list] computes the first unit differences of the data given in list as pairs of input/output values (the first entry in list may consist of the names for the variables)." SecondUnitDiff::usage = "SecondUnitDiff[list] computes the second unit differences of the data given in list as pairs of input/output values (the first entry in list may consist of the names for the variables)." UnitRatios::usage = "UnitRatios[list] computes the unit ratios of the data given in list as pairs of input/output values (the first entry in list may consist of the names for the variables)." DiffRatios::usage = "DiffRatios[list] computes the difference ratios of the data given in list as pairs of input/output values (the first entry in list may consist of the names for the variables)." ShiftInput::usage = "ShiftInput[list,s] shifts the input values of list by an amount of s, where list consists of pairs of input/output values (the first entry in list may contain the names of the variables)." ShiftOutput::usage = "ShiftOutput[list,s] shifts the output values of list by an amount of s, where list consists of pairs of input/output values (the first entry in list may contain the names of the variable)." Global`xplot::usage = "xplot gives the range for the input values to be displayed in graphs of functions fitted to data." Global`yplot::usage = "yplot gives the range for the output values to be displayed in graphs of functions fitted to data." Global`x::usage = "name for independent varaible in fitted functions" Begin["`Private`"] SineFitGraph::badarg= "The period must be a nonnegative number" SineFitFunc::badarg= "The period must be a nonnegative number" PlotData[ls_List]:= Module[{A = ls,xlabel = " ", ylabel = " ",xorg,yorg}, (* check whether names in first row *) If[NumberQ[N[ls[[1,1]]]]==False, xlabel = A[[1,1]]; ylabel = A[[1,2]]; A = Rest[ls]]; xorg = 1.1 Min[Transpose[A][[1]]]- .1 Max[Transpose[A][[1]]]; yorg = 1.1 Min[Transpose[A][[2]]]- .1 Max[Transpose[A][[2]]]; (*xorg = Min[Transpose[A][[1]]]; yorg = Min[Transpose[A][[2]]];*) graph =ListPlot[A,PlotStyle ->{PointSize[0.02],RGBColor[1,0,0]}, AxesLabel ->{xlabel,ylabel}, AxesOrigin ->{xorg,yorg}, DisplayFunction -> Identity]; Show[{graph, Graphics[{Line[{{xorg,yorg},{Min[Transpose[A][[1]]],yorg}}], Line[{{xorg,yorg},{xorg, Min[Transpose[A][[2]]]}}]}]}, DisplayFunction -> $DisplayFunction] ] PolyFitGraph[ls_List,n_Integer,opts___?OptionQ]:= Module[ {A = ls,funcs,p1,p2,xvals,xrange, yvals, yrange, xlabel = " ", ylabel = " ",xr,yr,fitfun}, (* check whether names in first row *) If[NumberQ[N[ls[[1,1]]]]==False, xlabel = A[[1,1]]; ylabel = A[[1,2]]; A = Rest[ls]]; (* Check on proper values for n *) Which[ n <= 0,Print["n needs to be a positive integer"], n >= Length[A], Print["There are not enough points to determine the fitted function uniquely - choose a smaller n"], (* compute range for output *) True, Clear[xx]; xvals = Transpose[A][[1]]; xrange = Max[xvals]- Min[xvals]; minx =Min[xvals]-.1 xrange; maxx = Max[xvals]+.1 xrange; yvals = Transpose[A][[2]]; yrange = Max[yvals]- Min[yvals]; miny =Min[yvals]-.1 yrange; maxy = Max[yvals]+.1yrange; {xr,yr}= {Global`xplot,Global`yplot} /.{opts} /.{Global`xplot -> {minx, maxx},Global`yplot -> {miny,maxy} }; (* actual curve fit *) funcs=Table[xx^i,{i,0,n}]; fitfun[x_]:= Chop[N[Fit[A,funcs,xx]]]/.{xx->x}; (* display of data and function *) p1=ListPlot[A,PlotStyle ->PointSize[0.015], DisplayFunction ->Identity]; p2 = Plot[fitfun[x],{x,xr[[1]],xr[[2]]}, AxesOrigin -> {xr[[1]],yr[[1]]}, AxesLabel ->{xlabel,ylabel}, DisplayFunction -> Identity, PlotStyle -> {RGBColor[0,0.55,0.75]}]; Show[p2,p1, PlotRange -> {xr,yr}, DisplayFunction -> $DisplayFunction]] ] PolyFitFunc[ls_List,n_Integer]:= Module[ {A = ls,funcs, fitfun}, (* check whether names in first row *) If[NumberQ[N[ls[[1,1]]]]==False, A = Rest[ls]]; (* Check on proper values for n *) Which[ n <= 0,Print["n needs to be a positive integer"], n >= Length[A], Print["There are not enough points to determine the fitted function uniquely - choose a smaller n"], (* compute range for output *) True, (* actual curve fit *) funcs=Table[xx^i,{i,0,n}]; Clear[Global`x]; Chop[N[Fit[A,funcs,xx]]]/.{xx->Global`x}] ] ExpoFitGraph[ls_List,opts___?OptionQ]:= Module[{A = ls,p1,p2,minx,maxx,xvals, xrange, miny,maxy,yvals, yrange, xlabel = " ", ylabel = " ",xr,yr,model, astart,bstart,data,input,output,fitfun}, (* check whether names in first row *) If[NumberQ[N[ls[[1,1]]]]==False, xlabel = A[[1,1]]; ylabel = A[[1,2]]; A = Rest[ls]]; (* Check on proper values for ls *) If[Length[A]<2, Print[ "There are not enough points to determine the function uniquely"], Clear[xx,a,b,c]; (* compute range for output *) xvals = Transpose[A][[1]]; xrange = Max[xvals]- Min[xvals]; minx =Min[xvals]-.1 xrange; maxx = Max[xvals]+.1 xrange; yvals = Transpose[A][[2]]; yrange = Max[yvals]- Min[yvals]; miny =Min[yvals]-.1 yrange; maxy = Max[yvals]+.1yrange; {xr,yr}= {Global`xplot,Global`yplot} /.{opts} /.{Global`xplot -> {minx, maxx},Global`yplot -> {miny, maxy} }; (* actual curve fit *) (* getting starting values for the parameters a and b*) input=Transpose[A][[1]]; If[Min[yvals] <= 0, Print[ "The horizontal asymptote has negative value. You need to shift \ the output values before using this function!"], (* else do fit *) output =Log[ Transpose[A][[2]]]; data = Transpose[{input,output}]; regressvals[x_]:= Fit[data,{1,xx},xx]/.{xx -> x}; a = E^regressvals[0]; b = E^(regressvals[1]-regressvals[0]); fitfun[x_]:= a*b^x; (* display of data and function *) p1=ListPlot[A,PlotStyle ->PointSize[0.015], DisplayFunction ->Identity]; p2 = Plot[fitfun[x],{x,xr[[1]],xr[[2]]}, AxesOrigin -> {xr[[1]],yr[[1]]}, AxesLabel ->{xlabel,ylabel}, DisplayFunction -> Identity, PlotStyle -> {RGBColor[.75,0,.3]}]; Show[p2,p1, PlotRange ->{xr,yr}, DisplayFunction -> $DisplayFunction]] ] ] (*End of Module *) ExpoFitFunc[ls_List]:= Module[{A = ls, astart,bstart,data,input,output}, (* check whether names in first row *) If[NumberQ[N[ls[[1,1]]]]==False, A = Rest[ls]]; (* Check on proper values for ls *) If[Length[A]<2, Print[ "There are not enough points to determine the function uniquely"], Clear[xx,a,b,c]; (* actual curve fit *) (* getting starting values for the parameters a and b*) input=Transpose[A][[1]]; If[Min[Transpose[A][[2]]] <= 0, Print[ "The horizontal asymptote has negative value. You need to shift \ the output values before using this function!"], (* else do fit *) output =Log[ Transpose[A][[2]]]; data = Transpose[{input,output}]; regressvals[x_]:= Fit[data,{1,xx},xx]/.{xx -> x}; a = E^regressvals[0]; b = E^(regressvals[1]-regressvals[0]); Clear[Global`x]; a*b^Global`x ] ] ] LogisticFitGraph[ls_List,opts___?OptionQ]:= Module[{A1,A2,A,p1,p2,xvals, xrange, yvals, yrange, xlabel = " ", ylabel = " ",diff, model,xr,yr,deltain,deltaout,outav,lim,U, data,minusa,N0,a,b,fitfun}, Off[General::spell1]; Off[General::spell]; (* check whether names in first row *) If[NumberQ[N[ls[[1,1]]]]==False, xlabel = ls[[1,1]]; ylabel = ls[[1,2]]; A1=Transpose[Sort[Rest[ls]]][[1]]; A2=Transpose[Sort[Rest[ls]]][[2]], (*else*) A1=Transpose[Sort[ls]][[1]]; A2=Transpose[Sort[ls]][[2]]]; A = Transpose[{A1,A2}]; diff = Sort[A2][[Length[A2]]]-Sort[A2][[Length[A2]-1]]; (* compute range for output *) xvals = Transpose[A][[1]]; xrange = Max[xvals]- Min[xvals]; minx =Min[xvals]-.1 xrange; maxx = Max[xvals]+.1 xrange; yvals = Transpose[A][[2]]; yrange = Max[yvals]- Min[yvals]; miny =Min[yvals]-.1 yrange; maxy = Max[yvals]+.1yrange; {xr,yr}= {Global`xplot,Global`yplot} /.{opts} /.{Global`xplot -> {minx, maxx},Global`yplot -> {miny, maxy} }; (* actual curve fit *) deltain = Rest[A1]-Drop[A1,-1]; deltaout = Rest[A2]-Drop[A2,-1]; outav = (Rest[A2]+Drop[A2,-1])/2; data = Transpose[{outav,deltaout/(outav*deltain)}]; regressvals[x_]:= Fit[data,{1,xx},xx]/.{xx -> x}; a = regressvals[0]; b = -(regressvals[1]-regressvals[0]); lim = a/b; If[lim < Max[A2],lim = 1.01(Max[A2]+.5 diff)]; U = A2/lim; out = Log[1/U - 1]; data = Transpose[{A1,out}]; regressvals2[x_]:= Fit[data,{1,xx},xx]/.{xx -> x}; (* Formula 2.2.32 *) N0 = lim/(E^(regressvals2[0])+1)//N; minusa=(regressvals2[1]-regressvals2[0])//N; fitfun[t_]:= lim (1+((lim/N0)-1)E^(minusa*t))^(-1); (* display of data and function *) p1=ListPlot[A,PlotStyle ->PointSize[0.015], DisplayFunction ->Identity]; p2 = Plot[fitfun[x],{x,xr[[1]],xr[[2]]}, AxesOrigin -> {xr[[1]],yr[[1]]}, AxesLabel ->{xlabel,ylabel}, DisplayFunction -> Identity, PlotStyle -> {RGBColor[.75,0,.3]}]; Show[p2,p1, PlotRange ->{xr,yr}, DisplayFunction -> $DisplayFunction] ] LogisticFitFunc[ls_List]:= Module[{A1,A2,A, diff, model,xr,yr,deltain,deltaout,outav,lim,U, data,minusa,N0,a,b }, Off[General::spell1]; Off[General::spell]; (* check whether names in first row *) If[NumberQ[N[ls[[1,1]]]]==False, A1=Transpose[Sort[Rest[ls]]][[1]]; A2=Transpose[Sort[Rest[ls]]][[2]], (*else*) A1=Transpose[Sort[ls]][[1]]; A2=Transpose[Sort[ls]][[2]]]; A = Transpose[{A1,A2}]; diff = Sort[A2][[Length[A2]]]-Sort[A2][[Length[A2]-1]]; (* actual curve fit *) deltain = Rest[A1]-Drop[A1,-1]; deltaout = Rest[A2]-Drop[A2,-1]; outav = (Rest[A2]+Drop[A2,-1])/2; data = Transpose[{outav,deltaout/(outav*deltain)}]; regressvals[x_]:= Fit[data,{1,xx},xx]/.{xx -> x}; a = regressvals[0]; b = -(regressvals[1]-regressvals[0]); lim = a/b; If[lim < Max[A2],lim = 1.01(Max[A2]+.5 diff)]; U = A2/lim; out = Log[1/U - 1]; data = Transpose[{A1,out}]; regressvals2[x_]:= Fit[data,{1,xx},xx]/.{xx -> x}; (* Formula 2.2.32 *) N0 = lim/(E^(regressvals2[0])+1)//N; minusa=(regressvals2[1]-regressvals2[0])//N; Clear[Global`x]; lim (1+((lim/N0)-1)E^(minusa*Global`x))^(-1) ] SineFitGraph[ls_List, period_, opts___?OptionQ]:= Module[{A = ls,p1,p2,xvals, xrange, yvals, yrange, fitfun, xlabel = " ", ylabel = " ", bstart = N[2 Pi/period], cstart = 0,dstart , ampstart, model,xr,yr}, Off[General::spell1]; Off[General::spell]; (* check whether names in first row *) If[NumberQ[N[ls[[1,1]]]]==False, xlabel = A[[1,1]]; ylabel = A[[1,2]]; A = Rest[ls]]; (* Check on proper values for ls and period *) If[ Length[A]<4, Print[ "There are not enough points to determine the function uniquely"], (*else*) If[period <=0, Print["The period has to be positive"], (* compute range for output *) xvals = Transpose[A][[1]]; xrange = Max[xvals]- Min[xvals]; minx =Min[xvals]-.1 xrange; maxx = Max[xvals]+.1 xrange; yvals = Transpose[A][[2]]; yrange = Max[yvals]- Min[yvals]; miny =Min[yvals]-.1 yrange; maxy = Max[yvals]+.1yrange; {xr,yr}= {Global`xplot,Global`yplot} /.{opts} /.{Global`xplot -> {minx, maxx},Global`yplot -> {miny, maxy} }; (* actual curve fit *) Off[NonlinearFit::lmpnocon]; Clear[xx,amp,b,c,d]; dstart = N[Max[A]+Min[A]]/2; ampstart = N[Max[A]-Min[A]]/2; pars = {{amp,ampstart},{b,bstart}, {c,cstart},{d,dstart}}; model = amp Sin[b xx - c] + d; fitfun[x_] := Chop[N[NonlinearFit[A,model,xx, pars]]] /. {xx -> x}; (* display of data and function *) p1=ListPlot[A,PlotStyle ->PointSize[0.015], DisplayFunction ->Identity]; p2 = Plot[fitfun[x],{x,xr[[1]],xr[[2]]}, AxesOrigin -> {xr[[1]],yr[[1]]}, AxesLabel ->{xlabel,ylabel}, DisplayFunction -> Identity, PlotStyle -> {RGBColor[0.5,0,.5]}]; Show[p2,p1, PlotRange -> {xr,yr}, DisplayFunction -> $DisplayFunction]]] ] SineFitFunc[ls_List, period_]:= Module[{A = ls, fitfun, bstart = N[2 Pi/period], cstart = 0,dstart , ampstart, model }, Off[General::spell1]; Off[General::spell]; (* check whether names in first row *) If[NumberQ[N[ls[[1,1]]]]==False, A = Rest[ls]]; (* Check on proper values for ls and period *) If[ Length[A]<4, Print[ "There are not enough points to determine the function uniquely"], (*else*) If[period <=0, Print["The period has to be positive"], (* actual curve fit *) Off[NonlinearFit::lmpnocon]; Clear[xx,amp,b,c,d]; dstart = N[Max[A]+Min[A]]/2; ampstart = N[Max[A]-Min[A]]/2; pars = {{amp,ampstart},{b,bstart}, {c,cstart},{d,dstart}}; model = amp Sin[b xx - c] + d; Clear[Global`x]; Chop[N[NonlinearFit[A,model,xx, pars]]] /. {xx -> Global`x}]] ] FirstUnitDiff[ls_List]:= Module[{A2,A1,outdiff,indiff}, (* check whether names in first row *) If[NumberQ[N[ls[[1,1]]]]==False, A1=Transpose[Sort[Rest[ls]]][[1]]; A2=Transpose[Sort[Rest[ls]]][[2]], (*else*) A1=Transpose[Sort[ls]][[1]]; A2=Transpose[Sort[ls]][[2]]]; (* computation of differences *) outdiff =MapThread[Plus,{Rest[A2],-Drop[A2,-1]}]; indiff = MapThread[Plus,{Rest[A1],-Drop[A1,-1]}]; outdiff/indiff] SecondUnitDiff[ls_List]:= Module[{A2,A1,FUD,outdiff,indiff}, (* check whether names in first row *) If[NumberQ[N[ls[[1,1]]]]==False, A1=Transpose[Sort[Rest[ls]]][[1]]; A2=Transpose[Sort[Rest[ls]]][[2]], (*else*) A1=Transpose[Sort[ls]][[1]]; A2=Transpose[Sort[ls]][[2]]]; (* computation of differences *) outdiff =MapThread[Plus,{Rest[A2],-Drop[A2,-1]}]; indiff = MapThread[Plus,{Rest[A1],-Drop[A1,-1]}]; FUD=outdiff/indiff; outdiff = MapThread[Plus,{Rest[FUD],-Drop[FUD,-1]}]; indiff = MapThread[Plus,{Drop[A1,2],-Drop[A1,-2]}]; outdiff/indiff] UnitRatios[ls_List]:= Module[{A2,A1,indiff, ratio,lis}, (* check whether first row consists of names *) If[NumberQ[N[ls[[1,1]]]]==False, A1=Transpose[Sort[Rest[ls]]][[1]]; A2=Transpose[Sort[Rest[ls]]][[2]], (*else*) A1=Transpose[Sort[ls]][[1]]; A2=Transpose[Sort[ls]][[2]]]; lis= Union[Position[A2,0],Position[A2,0.0]]; A1 = Delete[A1,lis]; A2 = Delete[A2,lis]; ratio =MapThread[Divide,{Rest[A2],Drop[A2,-1]}]; indiff = MapThread[Plus,{Rest[A1],-Drop[A1,-1]}]; ratio^(1/indiff)//N] DiffRatios[ls_]:=Module[{A=ls,outvals,invals,DR}, If[Head[ls]=!=TableForm &&Head[ls]=!=List, Print["Input must be a list or a table"], If[Head[ls]==TableForm,A=ls[[1]], A = ls]; If[NumberQ[N[A[[1,1]]]]==False, A = Rest[A]]; outvals=Transpose[A][[2]]; invals=Transpose[A][[1]]; DR=2(Rest[outvals]-Drop[outvals,-1])/ ((Rest[outvals]+Drop[outvals,-1])(Rest[invals]-Drop[invals,-1])); FirstUnitDiff[Transpose[{Drop[outvals,-1],DR}]]//N ]] ShiftInput[ls_List,shift_]:= Module[{A1,heading}, (* check whether first row consists of names *) If[NumberQ[N[ls[[1,1]]]]==False, Which[shift < 0,heading = StringJoin[{ls[[1,1]],ToString[shift]}], shift > 0,heading = StringJoin[{ls[[1,1]],"+",ToString[shift]}], True,heading = ls[[1,1]]]; A1=Prepend[Transpose[Rest[ls]][[1]]+shift ,heading], (*else*) A1=Transpose[ls][[1]]+shift]; A = Transpose[{A1,Transpose[ls][[2]]}]; A] ShiftOutput[ls_List,shift_]:= Module[{A1, heading}, (* check whether first row consists of names *) If[NumberQ[N[ls[[1,1]]]]==False, Which[shift < 0,heading = StringJoin[{ls[[1,2]],ToString[shift]}], shift > 0,heading = StringJoin[{ls[[1,2]],"+",ToString[shift]}], True,heading = ls[[1,2]]]; A1=Prepend[Transpose[Rest[ls]][[2]]+shift ,heading], (*else*) A1=Transpose[ls][[2]]+shift]; A = Transpose[{Transpose[ls][[1]],A1}]; A] (*End of Module*) FitComp[ls_List,funcs_List]:= Module[{ A = ls,xvals,TofA,funcvals={},m=Length[funcs], foo,l,toterror}, (* check whether names in first row *) If[NumberQ[N[ls[[1,1]]]]==True, PrependTo[A,{"input","output"}]]; TofA = Transpose[A]; xvals = Rest[TofA[[1]]]; yvals = Rest[TofA[[2]]]; l = Length[xvals]; Do[foo[xx_]:=funcs [[i]]/.Global`x->xx; funcvals = {}; txt = SequenceForm["f",Subscript[i],"(x)"]; PrependTo[funcvals,txt]; Print[txt," = ",foo[Global`x]]; Do[myx =xvals[[r]]; AppendTo[funcvals,Chop[foo[myx]]], {r,1,l}]; AppendTo[TofA,funcvals]; delta = Chop[Rest[funcvals] - yvals]; toterror = Apply[Plus,delta^2]; Print["Total Error for ",txt, "=",toterror]; PrependTo[delta, SequenceForm["\[CapitalDelta]",Subscript[i],"(x)"]]; AppendTo[TofA,delta], {i,1,m}]; Transpose[TofA]//TableForm] (*End of Module*) End[] EndPackage[]