function [outFORE] = forecastTS(outEST,varargin) %Forecast for a time series with trend, time varying seasonal, level shift and irregular component % %Link to the help function % % forecastTS produces forecasts for a time series with trend (up to third order), % seasonality (constant or of varying amplitude) with a different number of % harmonics and a level shifta and explanatory variables. % % Required input arguments: % % outEST : A structure containing the output of routine LTSts. % Structure. % Structure containing the following fields. % outEST.B = Matrix containing estimated beta coefficients, % (including the intercept when options.intercept=1) % standard errors, t-stat and p-values % The content of matrix B is as follows: % 1st col = beta coefficients % The order of the beta coefficients is as follows: % 1) trend elements (if present). If the trend is % of order two there are r+1 coefficients if the % intercept is present otherwise there are just r % components; % 2) linear part of seasonal component 2, 4, 6, ..., % s-2, s-1 coefficients (if present); % 3) coefficients associated with the matrix of % explanatory variables which have a potential effect % on the time series under study (X); % 4) non linear part of seasonal component, that is % varying amplitude. If varying amplitude is of order % k there are k coefficients (if present); % 5) level shift component (if present). In this case % there are two coefficients, the second (which is % also the last element of vector beta) is an integer % which specifies the time in which level shift takes % place and the first (which is also the penultime % element of vector beta) is a real number which % identifies the magnitude of the upward (downward) % level shift; % 2nd col = standard errors; % 3rd col = t-statistics; % 4th col = p values. % outEST.posLS = scalar associated with best tentative level shift % position. If this field does not exist, forecasts % are done assuming no level shift. % outEST.invXX = $cov(\beta)/\hat \sigma^2$. p-by-p, square matrix. % If the model is linear out.invXX is equal to % $(X'X)^{-1}$, else out.invXX is equal to $(A'A)^{-1}$ % where $A$ is the matrix of partial derivatives. More % precisely: % \[ % a_{i,j}=\frac{\partial \eta_i(\hat \beta)}{\partial \hat \beta_j} % \] % where % \begin{eqnarray} % y_i & = & \eta(x_i,\beta)+ \epsilon_i \\ % & = & \eta_i +\epsilon_i \\ % & = & \eta(x_i,\hat \beta)+ e_i \\ % & = & \hat \eta_i + e_i % \end{eqnarray} % outEST.yhat = vector of fitted values after final (NLS=non linear % least squares) step: % $ (\hat \eta_1, \hat \eta_2, \ldots, \hat \eta_T)'$ % outEST.scale = Final scale estimate of the residuals % \[ % \hat \sigma = cor \times \sum_{i \in S_m} [y_i- \eta(x_i,\hat \beta)]^2/(m-p) % \] % where $S_m$ is a set of cardinality $m$ which % contains the units not declared as outliers and $p$ % is the total number of estimated parameters and cor % is a correction factor to make the estimator % consistent. % REMARK: structure outEST can be conveniently created % by function LTSts. % Data Types - struct % % % Optional input arguments: % % model : model type. Structure. A structure which specifies the model % used to simulate the time series. The structure contains % the following fields: % model.trend = scalar (order of the trend component). % trend = 1 implies linear trend with intercept, % trend = 2 implies quadratic trend, etc. % If this field is empty the simulated time series % will not contain a trend. The default value % of model.trend is 1. % model.s = scalar greater than zero which specifies the % length of the seasonal period. For monthly % data (default) s=12, for quartely data s=4, ... % The default value of model.s is 12 (that is monthly % data are assumed) % model.seasonal = scalar (integer specifying number of % frequencies, i.e. harmonics, in the seasonal % component. Possible values for seasonal are % $1, 2, ..., [s/2]$, where $[s/2]=floor(s/2)$. % For example: % if seasonal = 1 (default) we have: % $\beta_1 \cos( 2 \pi t/s) + \beta_2 sin ( 2 \pi t/s)$; % if seasonal = 2 we have: % $\beta_1 \cos( 2 \pi t/s) + \beta_2 \sin ( 2 \pi t/s) % + \beta_3 \cos(4 \pi t/s) + \beta_4 \sin (4 \pi t/s)$. % Note that when $s$ is even the sine term disappears % for $j=s/2$ and so the maximum number of % trigonometric parameters is $s-1$. % If seasonal is a number greater than 100 then it % is possible to specify how the seasonal component % grows over time. % For example, seasonal = 101 implies a seasonal % component which just uses one frequency % which grows linearly over time as follows: % $(1+\beta_3 t)\times ( \beta_1 cos( 2 \pi t/s) + % \beta_2 \sin ( 2 \pi t/s))$. % For example, seasonal = 201 implies a seasonal % component which just uses one frequency % which grows in a quadratic way over time as % follows: % $(1+\beta_3 t + \beta_4 t^2)\times( \beta_1 \cos( % 2 \pi t/s) + \beta_2 \sin ( 2 \pi t/s))$. % If this field is an empty double (default) the % simulated time series will not contain a seasonal % component. % model.X = explanatory variabels. Matrix of size T-by-nexpl. If model.X % is a matrix of size T-by-nexpl, it contains the % values of nexpl extra covariates which % affect y. % If this field is an empty double (default) there is % no effect of explanatory variables. % Example - 'model', model % Data Types - struct % % nfore : number of forecasts. Scalar. % Positive integer which defines the number of forecasts. The % default value of nfore is 24. % Example - 'nfore',12 % Data Types - double % % conflev : confidence level for the confidence bands. Scalar. % A number between 0 and 1 which defines the confidence % level which is used to produce the bands. The default % value of conflev is 0.99. % Example - 'conflev',0.999 % Data Types - double % % plots : Plots on the screen. Scalar. % If plots == 1 a plot with the real time series with fitted % values and forecasts (with confidence bands) will appear on % the screen. % The default value of plot is 0, that is no plot is shown on % the screen. % Example - 'plots',1 % Data Types - double % % StartDate : The time of the first observation. % Numeric vector of length 2. Vector with two integers, which % specify a natural time unit and a (1-based) number of % samples into the time unit. For example, if model.s=12 % (that is the data are monthly) and the first observation % starts in March 2016, then StartDate=[2016,3]; Similarly, % if models.s=4 (that is the data are quarterly) and the first % observation starts in the second quarter or year 2014, then % StartData=[2014,2]. The information in option StartDate % will be used to create in the output the dates inside the % time series object. % Example - 'StartDate',[2016,3] % Data Types - double % % % FileNameOutput : save simulated time series to txt file. Character. % If FileNameOutput is empty (default) nothing is saved on % the disk, else FileNameOutput will contain the path where % to save the file on the disk. % Example - 'FileNameOutput',['C:' filesep 'myoutput' fielsep 'savesimdata.txt'] % Data Types - Character % % dispresults : Display results of final fit. Boolean. If dispresults is % true, labels of coefficients, estimated coefficients, % standard errors, tstat and p-values are shown on the % screen in a fully formatted way. The default value of % dispresults is false. % Example - 'dispresults',true % Data Types - logical % % Output: % % outFORE: structure which contains the following fields: % % outFORE.signal = vector of length (length(y)+nfore) containing % predictive values in sample and out of sample. % Predictive values = TR+SE+X+LS. % outFORE.trend = vector of length (length(y)+nfore) containing % estimated trend (TR) in sample and out of sample. % If this component is not present, it is equal to 0. % outFORE.seasonal = vector of length (length(y)+nfore) containing % estimated seasonal component (SE) in sample and out of sample. % If this component is not present, it is equal to 0. % outFORE.lshift = vector of length (length(y)+nfore) containing % level shift (LS) in sample and out of sample. % If this component is not present, it is equal to 0. % outFORE.X = vector of length (length(y)+nfore) % containing the effecf of the explanatory variables. % If this component is not present, it is equal to 0. % outFORE.confband = matrix of size (length(y)+nfore)-by-2 % containing lower and upper confidence bands of the % forecasts. The confidence level of the bands is % splecified is input parameter conflev. Note that the % first length(y) rows of this matrix are equal to NaN. % % % % See also LTSts, wedgeplot, simulateTS % % References: % % Rousseeuw, P.J., Perrotta D., Riani M., Hubert M. (2018), Robust % Monitoring of Time Series with Application to Fraud Detection, % Submitted. % % % Copyright 2008-2017. % Written by Marco Riani, Domenico Perrotta, Peter Rousseeuw and Mia Hubert % % %Link to the help function % %$LastChangedDate:: 2018-02-19 17:38:15 #$: Date of the last commit % Examples: %{ %% Linear time varying seasonal component. close all rng(1) model=struct; model.trend=1; model.seasonal=103; modelSIM=model; modelSIM.trendb=[0 0]; modelSIM.seasonalb=40*[0.1 -0.5 0.2 -0.3 0.3 -0.1 0.222]; modelSIM.signal2noiseratio=20; T=100; % Simulate outSIM=simulateTS(T,'model',modelSIM,'plots',1); ySIM=outSIM.y; % Estimate outEST=LTSts(ySIM,'model',model,'plots',1); % Forecast outFORE=forecastTS(outEST,'model',model,'plots',1); %} %{ %% Quadratic trend and constant seasonal. close all rng(1) model=struct; model.trend=2; model.seasonal=3; modelSIM=model; modelSIM.trendb=[100 10 -0.05]; modelSIM.seasonalb=400*[0.1 -0.5 0.2 -0.3 0.3 -0.1]; modelSIM.signal2noiseratio=1; T=100; % Simulate outSIM=simulateTS(T,'model',modelSIM,'plots',1); ySIM=outSIM.y; % Estimate outEST=LTSts(ySIM,'model',model,'plots',1); % Forecast outFORE=forecastTS(outEST,'model',model,'plots',1); %} %{ %% Simulated time series with quadratic trend, fixed seasonal and level shift. % A time series of 100 observations is simulated from a model which % contains a quadratic trend, a seasonal component with two harmonics % no explanatory variables and a level shift in position 30 with size % 5000 and a signal to noise ratio egual to 20 close all rng(1) model=struct; model.trend=2; model.seasonal=2; model.lshift=30; modelSIM=model; modelSIM.trendb=[5,10,-3]; modelSIM.seasonalb=100*[2 4 0.1 8]; modelSIM.signal2noiseratio=20; modelSIM.lshiftb=10000; T=100; % Simulate outSIM=simulateTS(T,'model',modelSIM,'plots',1); ySIM=outSIM.y; % Estimate % model.lshift=5 implies that LS is investigated from position 5 model.lshift=5; outEST=LTSts(ySIM,'model',model,'plots',1,'msg',0); % Forecast outFORE=forecastTS(outEST,'model',model,'plots',1); %} %{ %% Contaminated airline data (1). % 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 y = [112 115 145 171 196 204 242 284 315 340 360 417 % Jan 118 126 150 180 196 188 233 277 301 318 342 391 % Feb 132 141 178 193 236 235 267 317 356 362 406 419 % Mar 129 135 163 181 235 227 269 313 348 348 396 461 % Apr 121 125 172 183 229 234 270 318 355 363 420 472 % May 135 149 178 218 243 264 315 374 422 435 472 535 % Jun 148 170 199 230 264 302 364 413 465 491 548 622 % Jul 148 170 199 242 272 293 347 405 467 505 559 606 % Aug 136 158 184 209 237 259 312 355 404 404 463 508 % Sep 119 133 162 191 211 229 274 306 347 359 407 461 % Oct 104 114 146 172 180 203 237 271 305 310 362 390 % Nov 118 140 166 194 201 229 278 306 336 337 405 432 ]; % Dec y=y(:); % Contaminate the first 20 observations y(1:20)=y(1:20)+200; close all % Model with linear trend, three harmonics for seasonal component and % varying amplitude using a linear trend. Search for a level shift model=struct; model.trend=1; % linear trend model.s=12; % monthly time series model.seasonal=103; % six harmonics with linear time varying seasonality model.lshift=10; % search for level shift out=LTSts(y,'model',model,'plots',1,'dispresults',true,'msg',0); % 3 years forecasts nfore=36; StartDate=[1949 1]; conflev=0.999; % Wide confidence level for the forecast outFORE=forecastTS(out,'model',model,'nfore',nfore,'StartDate',StartDate,'conflev',conflev); %} %{ %% Contaminated airline data (2). close all % In this example we estimate a model without the seasonal component % 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 y = [112 115 145 171 196 204 242 284 315 340 360 417 % Jan 118 126 150 180 196 188 233 277 301 318 342 391 % Feb 132 141 178 193 236 235 267 317 356 362 406 419 % Mar 129 135 163 181 235 227 269 313 348 348 396 461 % Apr 121 125 172 183 229 234 270 318 355 363 420 472 % May 135 149 178 218 243 264 315 374 422 435 472 535 % Jun 148 170 199 230 264 302 364 413 465 491 548 622 % Jul 148 170 199 242 272 293 347 405 467 505 559 606 % Aug 136 158 184 209 237 259 312 355 404 404 463 508 % Sep 119 133 162 191 211 229 274 306 347 359 407 461 % Oct 104 114 146 172 180 203 237 271 305 310 362 390 % Nov 118 140 166 194 201 229 278 306 336 337 405 432 ]; % Dec y=y(:); % Contaminate the first 20 observations y(1:20)=y(1:20)+200; % Model with linear trend and no seasonal component. Search for a level shift model=struct; model.trend=1; % linear trend model.s=12; % monthly time series model.seasonal=[]; % no seasonal component model.lshift=10; % search for level shift out=LTSts(y,'model',model,'plots',1,'dispresults',true,'msg',0); % 3 years forecasts nfore=36; StartDate=[1949 1]; conflev=0.999; % Wide confidence level for the forecast outFORE=forecastTS(out,'model',model,'nfore',nfore,'StartDate',StartDate,'conflev',conflev); %} %% Beginning of code if nargin<1 error('FSDA:forecastTS:MissingInputs','Input structure is missing'); end % Set up values for default model modeldef = struct; modeldef.trend = 1; modeldef.s = 12; % monthly time series modeldef.seasonal = []; modeldef.X = []; % no explanatory variables modeldef.lshift = []; % no level shift nocheck = false; plots = 1; FileNameOutput = ''; StartDate = ''; dispresults =false; nfore =24; % number of forecasts conflev = 0.99; % default confidence level for the forecasts options=struct('model',modeldef,... 'dispresults',dispresults,'nfore',nfore,'plots',plots,... 'FileNameOutput',FileNameOutput,'StartDate',StartDate,'conflev',conflev); %% User options UserOptions=varargin(1:2:length(varargin)); if ~isempty(UserOptions) % Check if number of supplied options is valid if length(varargin) ~= 2*length(UserOptions) error('FSDA:forecastTS:WrongInputOpt','Number of supplied options is invalid. Probably values for some parameters are missing.'); end % Check if all the specified optional arguments were present in % structure options Remark: the nocheck option has already been dealt % by routine chkinputR inpchk=isfield(options,UserOptions); WrongOptions=UserOptions(inpchk==0); if ~isempty(WrongOptions) disp(strcat('Non existent user option found->', char(WrongOptions{:}))) error('FSDA:forecastTS:NonExistInputOpt','In total %d non-existent user options found.', length(WrongOptions)); end % Write in structure 'options' the options chosen by the user for i=1:2:length(varargin) options.(varargin{i})=varargin{i+1}; end plots=options.plots; dispresults=options.dispresults; nfore=options.nfore; FileNameOutput=options.FileNameOutput; StartDate=options.StartDate; conflev=options.conflev; end % Default values for the optional parameters are set inside structure % 'options' if ~isequal(options.model,modeldef) fld=fieldnames(options.model); if nocheck == false % Check if user options inside options.model are valid options chkoptions(modeldef,fld) end for i=1:length(fld) modeldef.(fld{i})=options.model.(fld{i}); end end model = modeldef; % Get model parameters trend = model.trend; % get kind of trend s = model.s; % get periodicity of time series seasonal = model.seasonal; % get number of harmonics if isfield(outEST,'posLS') lshift = outEST.posLS; else lshift=0; end y=outEST.y; n=length(y); T = n+nfore; % seq is the vector which will contain linear time trend seq = (1:T)'; one = ones(T,1); % Construct the matrices which are fixed in each step of the minimization % procedure Seq = [one seq seq.^2 seq.^3]; % Define matrix which contains linear quadratic of cubic trend Xtrend = Seq(:,1:trend+1); ntrend = size(Xtrend,2); % seasonal component if seasonal >0 sstring=num2str(seasonal); if seasonal>100 varampl=str2double(sstring(1)); seasonal=str2double(sstring(2:3)); else varampl=0; end if seasonal < 1 || seasonal >floor(s/2) error('FSDA:forecastTS:WrongInput',['Seasonal component must be an integer between 1 and ' num2str(floor(s/2))]) end Xseaso=zeros(T,seasonal*2); for j=1:seasonal Xseaso(:,2*j-1:2*j)=[cos(j*2*pi*seq/s) sin(j*2*pi*seq/s)]; end % Remark: when s is even the sine term disapperas for j=s/2 and so the % maximum number of trigonometric terms is s-1 if seasonal==(s/2) Xseaso=Xseaso(:,1:end-1); end nseaso=size(Xseaso,2); else Xseaso=[]; nseaso=0; varampl=0; end X=model.X; isemptyX=isempty(X); if isemptyX % nexpl = number of potential explanatory variables nexpl=0; else nexpl=size(X,2); end % Define the explanatory variable associated to the level shift component if lshift>0 % Xlshift = explanatory variable associated with % level shift Xlshift is 0 up to lsh-1 and 1 from % lsh to T Xlshift= [zeros(outEST.posLS-1,1);ones(T-outEST.posLS+1,1)]; else Xlshift =[]; end % pini = number of parameters in the linear model without level shifts nor % varying amplitude % ntrend = number of trend parameters, % nseaso = number of parameters associated with the harmonics, % nexpl = number of explanatory variables, pini=ntrend+nseaso+nexpl; % p = total number of parameters in the model % nini + % varampl = number of parameters involving time varying trend, % + 2 additional parameters is there is a level shift component p=pini+varampl+(lshift>0); % Now compute again vector yhat using final vector betaout bsb=seq; betaout=outEST.B(:,1); if length(betaout)~=p disp('Warning: number of supplied regression parameters is not in agreement with those of input structure model') disp(['Number of supplied regression parameters=' num2str(length(betaout))]) disp(['Number of parameters in input strcuture model=' num2str(p)]) disp('...') error('FSDA:forecast:WrongInput','Wrong input model') end [yhat,yhattrend,yhatseaso,yhatX,yhatlshift]=lik(betaout); % yfitFS = likyhat(betaout,Xtrendf); yfitFS = yhat; if varampl==0 J=[Xtrend Xseaso X Xlshift]; else fdiffstep=[]; J = getjacobianFS(betaout,fdiffstep,@lik,yfitFS); end confband=NaN(length(yhat),2); if ~isempty(conflev) invXX=outEST.invXX; quant=tinv(1-(1-conflev)/2,n-length(betaout)); for i=n+1:n+nfore se=quant*outEST.scale*sqrt(J(i,:)*invXX*(J(i,:)')); confband(i,1)=yhat(i)-se; confband(i,2)=yhat(i)+se; end end outFORE=struct; outFORE.signal=yhat; outFORE.trend=yhattrend; outFORE.seasonal=yhatseaso; outFORE.X=yhatX; outFORE.lshift=yhatlshift; outFORE.confband=confband; yhatwithbands=[yhat confband]; % Write to file the simulated data if ~isempty(FileNameOutput) dlmwrite(FileNameOutput,yhatwithbands,'delimiter','\t','precision','%.12f') end % label the x axis using appropriate dates if ~isempty(StartDate) IniYear=StartDate(1); FinalYear=IniYear+ceil(T/s); [years, months] = meshgrid(IniYear:FinalYear, 1:12/s:12); years=years(1:T); months=months(1:T); % Convert date and time to serial date number datesnumeric=datenum(years(:), months(:), 1); else datesnumeric=1:T; end if dispresults b_trend={'b_trend1'; 'b_trend2'; 'b_trend3'; 'b_trend4'}; b_seaso={'b_cos1'; 'b_sin1'; 'b_cos2'; 'b_sin2'; 'b_cos3'; 'b_sin3'; ... 'b_cos4'; 'b_sin4'; 'b_cos5'; 'b_sin5'; 'b_cos6'}; b_expl={'b_X1'; 'b_X2'; 'b_X3'; 'b_X4'; 'b_X5'; 'b_X6'}; b_varampl={'b_varampl'; 'b_varamp2'; 'b_varamp3'}; b_lshift={'b_lshift'; 't_lshift'}; if seasonal>0 if 2*seasonal==s lab=[b_trend(1:trend+1); b_seaso]; else lab=[b_trend(1:trend+1); b_seaso(1:2*seasonal)]; end else lab=b_trend(1:trend+1); end if nexpl>0 lab=[lab;b_expl(1:nexpl)]; end if varampl>0 lab=[lab;b_varampl(1:varampl)]; end if lshift>0 lab=[lab; b_lshift(1)]; end bhat=outEST.B(:,1); se=outEST.B(:,2); tstat=outEST.B(:,3); pval=outEST.B(:,4); if verLessThan ('matlab','8.2.0') disp(' Coeff. SE t-stat p-values'); disp( [char(lab) num2str([bhat se tstat pval])]); else disp([table(lab) table(bhat) table(se) table(tstat) table(pval)]); end if lshift>0 disp(['Level shift position t=' num2str(outEST.posLS)]) end end if plots==1 figure; yfore=outFORE.signal; % Plot original time series plot(datesnumeric(1:n),y,'k') hold('on') % plot the original series plot(datesnumeric(1:n),yfore(1:n),'b-') % plot the forecasts plot(datesnumeric(n+1:n+nfore),yfore(n+1:end),'r') % plot the signal (TR+LS+X) % plot(datesnumeric,outFORE.trend+outFORE.lshift+outFORE.X,'color','m') title('Fit and forecasts from LTS','interpreter','LaTex','FontSize',14) plot(datesnumeric(n+1:n+nfore),confband(n+1:end,:),'r--') if ~isempty(StartDate) datetick('x','mmm-yy'); set(gca,'XTickLabelRotation',90); end ax=axis; ylimits=ax(3:4); line(0.5*sum(datesnumeric(n:n+1))*ones(2,1),ylimits,'color','g') end function [yhat,yhattrend,yhatseaso,yhatX,yhatlshift]=lik(beta0) yhattrend=Xtrend(bsb,:)*beta0(1:trend+1); npar=trend+1; if seasonal >0 if seasonal0 Xtre=1+Seq(bsb,2:varampl+1)*beta0((npar+1+nexpl):(npar+varampl+nexpl)); yhatseaso=Xtre.*yhatseaso; npar=npar+varampl; end else yhatseaso=0; end if isemptyX yhatX=0; else % Note the order of coefficients is trend, linear part of % seasonal component, expl variables, non linear part of % seasonal component, level shift yhatX=X(bsb,:)*beta0(npar+1-varampl:npar+nexpl-varampl); npar=npar+nexpl; end if lshift >0 % \beta_(npar+1)* I(t \geq \beta_(npar+2)) where beta_(npar+1) % is a real number and \beta_(npar+2) is a integer which % denotes the period in which level shift shows up yhatlshift=beta0(npar+1)*Xlshift(bsb); else yhatlshift=0; end % Fitted values from trend (yhattrend), (time varying) seasonal % (yhatseaso), explanatory variables (yhatX) and level shift % component (yhatlshift) yhat=yhattrend+yhatseaso+yhatX+yhatlshift; end function J = getjacobianFS(beta,fdiffstep,modelFS,yfit) function yplus = call_model_nested(betaNew) yplus = modelFS(betaNew); end J = statjacobianFS(@call_model_nested, beta, fdiffstep, yfit); end % function getjacobian function J = statjacobianFS(func, theta, DerivStep, y0) %STATJACOBIAN Estimate the Jacobian of a function % J is a matrix with one row per observation and one column per model % parameter. J(i,j) is an estimate of the derivative of the i'th % observation with respect to the j'th parameter. % For performance reasons, very little error checking is done on the input % arguments. This function makes the following assumptions about inputs: % % * func is the model function and is a valid function handle that accepts % a single input argument of the same size as theta. % * theta is vector or matrix of parameter values. If a matrix, each row % represents a different group or observation (see "Grouping Note" below) % and each column represents a different model parameter. % * DerivStep (optional) controls the finite differencing step size. It may % be empty, scalar, or a vector of positive numbers with the number of % elements equal to the number model parameters. % * y0 (optional) is the model function evaluated at theta. A value of [] % is equivalent to omitting the argument and results in the model being % evaluated one additional time. % % Example 1: NLINFIT % NLINFIT is used to estimate the parameters b(1) and b(2) for the model % @(b,T) b(1)*sin(b(2)*T), given data at T=1:5. NLINFIT needs the % Jacobian of the model function with respect to b(1) and b(2) at each T. % To do this, it constructs a new function handle that is only a function % of b and that "burns-in" the value of T (e.g. model2 = @(b) model1(b,T)). % It then calls STATJACOBIAN with the new function handle to obtain a % matrix J, where J(i,j) is an estimate of the derivative of the model % with respect to the j'th parameter evaluated at T(i) and b. % % Example 2: NLMEFIT or NLMEFITSA with group-specific parameters % NLMEFIT requires the Jacobian of the model function with respect to two % parameters evaluated at group-specific values. (Group-specific % parameters can arise, for example, from using the default FEConstDesign % and REConstDesign options.) NLMEFIT calls STATJACOBIAN passing in a % matrix of parameter values theta, with one row per group, where % theta(i,j) represents a parameter value for i'th group and j'th % parameter. STATJACOBIAN returns a matrix J, where J(i,j) is an estimate % of the derivative of the model with respect to the j'th parameter, % evaluated for observation i with parameter values theta(rowIdx(i),:), % which are the parameter values for the observation's group. % % Example 3: NLMEFIT with observation-specific parameters % NLMEFIT requires the Jacobian of the model function with respect to two % parameters evaluated at observation-specific values. (Observation- % specific parameters can arise, for example, from using the FEObsDesign % or REObsDesign options.) NLMEFIT calls STATJACOBIAN passing in a matrix % of parameter values theta, with one row per observation, where % theta(i,j) represents a parameter value for the i'th observation and % j'th parameter. In this case, rowIdx is 1:N, where N is the number of % observations. STATJACOBIAN returns a matrix J, where J(i,j) is an % estimate of the derivative of the model with respect to the j'th % parameter, evaluated for observation i with parameter values % theta(i,:), which are the parameter values for the observation. % Use the appropriate class for variables. classname = class(theta); % Handle optional arguments, starting with y0 since it will be needed to % determine the appropriate size for a default groups. if nargin < 4 || isempty(y0) y0 = func(theta); end % When there is only one group, ensure that theta is a row vector so % that vectoriation works properly. Also ensure that the underlying % function is called with an input with the original size of theta. thetaOriginalSize = size(theta); theta = reshape(theta, 1, []); func = @(theta) func(reshape(theta, thetaOriginalSize)); % All observations belong to a single group; scalar expansion allows us % to vectorize using a scalar index. rowIdx = 1; [numThetaRows, numParams] = size(theta); if nargin < 3 || isempty(DerivStep) % Best practice for forward/backward differences: DerivStep = repmat(sqrt(eps(classname)), 1, numParams); % However, NLINFIT's default is eps^(1/3). elseif isscalar(DerivStep) DerivStep = repmat(DerivStep, 1, numParams); end delta = zeros(numThetaRows, numParams, classname); J = zeros(numel(y0), numParams, classname); for ii = 1:numParams % Calculate delta(:,ii), but remember to set it back to 0 at the end of the loop. delta(:,ii) = DerivStep(ii) * theta(:,ii); deltaZero = delta(:,ii) == 0; if any(deltaZero) % Use the norm as the "scale", or 1 if the norm is 0. nTheta = sqrt(sum(theta(deltaZero,:).^2, 2)); delta(deltaZero,ii) = DerivStep(ii) * (nTheta + (nTheta==0)); end thetaNew = theta + delta; yplus = func(thetaNew); dy = yplus(:) - y0(:); J(:,ii) = dy./delta(rowIdx,ii); delta(:,ii) = 0; end end end %FScategory:REG-Regression