@@ -700,3 +700,152 @@ function disp (this)
700700 out = b;
701701 endif
702702endfunction
703+
704+ % !demo
705+ % ! ## Fit an exponential growth model y = b1 * exp (b2 * x) and inspect it.
706+ % ! x = (1:10)';
707+ % ! y = [2.1; 2.9; 4.2; 5.3; 7.1; 9.4; 12.8; 16.5; 22.1; 29.8];
708+ % ! modelfun = @(b, x) b(1) .* exp (b(2) .* x);
709+ % ! mdl = fitnlm (x, y, modelfun, [1; 0.3]);
710+ % ! disp (mdl.Coefficients)
711+ % ! printf ("RMSE = %g, R^2 = %g\n", mdl.RMSE, mdl.Rsquared.Ordinary);
712+
713+ ## Comprehensive property and method coverage
714+ % !shared X, y, modelfun, beta0
715+ % ! X = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10];
716+ % ! y = [2.1; 2.9; 4.2; 5.3; 7.1; 9.4; 12.8; 16.5; 22.1; 29.8];
717+ % ! modelfun = @(b, x) b(1) .* exp (b(2) .* x);
718+ % ! beta0 = [1; 0.3];
719+
720+ % !test # coefficient table (estimate, SE, tStat) verified against MATLAB
721+ % ! mdl = fitnlm (X, y, modelfun, beta0);
722+ % ! assert_equal (mdl.Coefficients.Estimate, [1.683747025; 0.286911087], 1e-6);
723+ % ! assert_equal (mdl.Coefficients.SE, [0.035194899; 0.002350913], 1e-6);
724+ % ! assert_equal (mdl.Coefficients.tStat, [47.8406555; 122.042406], -1e-4);
725+ % ! assert_equal (mdl.Coefficients.tStat, ...
726+ % ! mdl.Coefficients.Estimate ./ mdl.Coefficients.SE, 1e-8);
727+
728+ % !test # sums of squares and their internal relationships
729+ % ! mdl = fitnlm (X, y, modelfun, beta0);
730+ % ! bhat = mdl.Coefficients.Estimate; fit = modelfun (bhat, X);
731+ % ! assert_equal (mdl.Fitted, fit, 1e-8);
732+ % ! assert_equal (mdl.SSE, sum ((y - fit) .^ 2), 1e-8);
733+ % ! assert_equal (mdl.SST, sum ((y - mean (y)) .^ 2), 1e-6);
734+ % ! assert_equal (mdl.SSR, sum ((fit - mean (y)) .^ 2), 1e-6);
735+ % ! assert_equal (mdl.SSE, 0.233771954, 1e-7);
736+ % ! assert_equal (mdl.SST, 750.976, 1e-3);
737+
738+ % !test # MSE/RMSE/DFE and the coefficient of determination
739+ % ! mdl = fitnlm (X, y, modelfun, beta0);
740+ % ! assert_equal (mdl.DFE, 8);
741+ % ! assert_equal (mdl.MSE, mdl.SSE / mdl.DFE, 1e-12);
742+ % ! assert_equal (mdl.RMSE, sqrt (mdl.MSE), 1e-12);
743+ % ! assert_equal (mdl.RMSE, 0.170942956, 1e-7);
744+ % ! assert_equal (mdl.Rsquared.Ordinary, 1 - mdl.SSE / mdl.SST, 1e-12);
745+ % ! assert_equal (mdl.Rsquared.Ordinary, 0.999688709, 1e-8);
746+ % ! assert_equal (mdl.Rsquared.Adjusted, 0.999649798, 1e-8);
747+
748+ % !test # log-likelihood and information criteria (values and identities)
749+ % ! mdl = fitnlm (X, y, modelfun, beta0);
750+ % ! ll = mdl.LogLikelihood; k = mdl.NumEstimatedCoefficients; n = 10;
751+ % ! assert_equal (ll, 4.590586096, 1e-6);
752+ % ! assert_equal (mdl.ModelCriterion.AIC, -5.181172193, 1e-6);
753+ % ! assert_equal (mdl.ModelCriterion.BIC, -4.576002007, 1e-6);
754+ % ! assert_equal (mdl.ModelCriterion.AIC, -2 * ll + 2 * k, 1e-9);
755+ % ! assert_equal (mdl.ModelCriterion.BIC, -2 * ll + k * log (n), 1e-9);
756+ % ! assert_equal (mdl.ModelCriterion.AICc, ...
757+ % ! -2 * ll + 2 * k + 2 * k * (k + 1) / (n - k - 1), 1e-9);
758+
759+ % !test # count/size properties and default names
760+ % ! mdl = fitnlm (X, y, modelfun, beta0);
761+ % ! assert_equal (mdl.NumCoefficients, 2);
762+ % ! assert_equal (mdl.NumEstimatedCoefficients, 2);
763+ % ! assert_equal (mdl.NumPredictors, 1);
764+ % ! assert_equal (mdl.NumObservations, 10);
765+ % ! assert_equal (mdl.CoefficientNames, {'b1', 'b2'});
766+ % ! assert_equal (mdl.ResponseName, "y");
767+
768+ % !test # the coefficient covariance is symmetric with SE^2 on the diagonal
769+ % ! mdl = fitnlm (X, y, modelfun, beta0);
770+ % ! C = mdl.CoefficientCovariance;
771+ % ! assert_equal (size (C), [2, 2]);
772+ % ! assert_equal (C, C', 1e-14);
773+ % ! assert_equal (diag (C), mdl.Coefficients.SE .^ 2, 1e-12);
774+
775+ % !test # raw residuals are response minus fit
776+ % ! mdl = fitnlm (X, y, modelfun, beta0);
777+ % ! assert_equal (class (mdl.Residuals), "table");
778+ % ! assert_equal (mdl.Residuals.Raw, y - mdl.Fitted, 1e-10);
779+
780+ % !test # predict returns fitted values (verified against MATLAB) with CIs
781+ % ! mdl = fitnlm (X, y, modelfun, beta0);
782+ % ! [yhat, yci] = predict (mdl, [2.5; 5.5; 8.5]);
783+ % ! assert_equal (yhat, [3.449741842; 8.158274281; 19.293455074], 1e-6);
784+ % ! assert_equal (yci(:,1), [3.329126146; 7.997938483; 19.121921613], 1e-5);
785+ % ! assert_equal (yci(:,2), [3.570357538; 8.318610079; 19.464988535], 1e-5);
786+ % ! assert_equal (all (yci(:,1) <= yhat & yhat <= yci(:,2)), true);
787+
788+ % !test # predict at the training data reproduces the fitted response
789+ % ! mdl = fitnlm (X, y, modelfun, beta0);
790+ % ! assert_equal (predict (mdl, X), mdl.Fitted, 1e-8);
791+
792+ % !test # feval agrees with predict; random draws match the response size
793+ % ! mdl = fitnlm (X, y, modelfun, beta0);
794+ % ! assert_equal (feval (mdl, [2.5; 5.5]), predict (mdl, [2.5; 5.5]), 1e-12);
795+ % ! ysim = random (mdl);
796+ % ! assert_equal (size (ysim), [10, 1]);
797+
798+ % !test # coefCI matches beta +/- t * SE and honours a custom alpha
799+ % ! mdl = fitnlm (X, y, modelfun, beta0);
800+ % ! b = mdl.Coefficients.Estimate; se = mdl.Coefficients.SE;
801+ % ! t95 = tinv (0.975, mdl.DFE);
802+ % ! assert_equal (coefCI (mdl), [b - t95 * se, b + t95 * se], 1e-12);
803+ % ! t90 = tinv (0.95, mdl.DFE);
804+ % ! assert_equal (coefCI (mdl, 0.10), [b - t90 * se, b + t90 * se], 1e-12);
805+
806+ % !test # coefTest reports a Wald F statistic versus the zero model
807+ % ! mdl = fitnlm (X, y, modelfun, beta0);
808+ % ! [p, F, df] = coefTest (mdl);
809+ % ! assert_equal (df, 2);
810+ % ! assert_equal (F > 1e5, true);
811+ % ! assert_equal (p < 1e-10, true);
812+
813+ % !test # table input gives the same fit as matrix input
814+ % ! tbl = table (X, y, "VariableNames", {'x', 'y'});
815+ % ! mdl = fitnlm (tbl, modelfun, beta0);
816+ % ! assert_equal (mdl.Coefficients.Estimate, [1.683747025; 0.286911087], 1e-6);
817+ % ! assert_equal (mdl.CoefficientNames, {'b1', 'b2'});
818+
819+ % !test # custom coefficient names are stored and used
820+ % ! mdl = fitnlm (X, y, modelfun, beta0, "CoefficientNames", {'A', 'k'});
821+ % ! assert_equal (mdl.CoefficientNames, {'A', 'k'});
822+
823+ % !test # disp prints the model header and the coefficient table
824+ % ! mdl = fitnlm (X, y, modelfun, beta0);
825+ % ! s = evalc ("disp (mdl)");
826+ % ! assert_equal (isempty (strfind (s, "Nonlinear regression model")), false);
827+ % ! assert_equal (isempty (strfind (s, "Estimate")), false);
828+
829+ % !test # chained subsref reaches property -> table column -> element
830+ % ! mdl = fitnlm (X, y, modelfun, beta0);
831+ % ! assert_equal (numel (mdl.Coefficients.Estimate), 2);
832+ % ! assert_equal (mdl.Coefficients.Estimate(1), 1.683747025, 1e-6);
833+
834+ % !test # the residual and slice plots run without error
835+ % ! mdl = fitnlm (X, y, modelfun, beta0);
836+ % ! hf = figure ("visible", "off");
837+ % ! unwind_protect
838+ % ! plotResiduals (mdl);
839+ % ! plotResiduals (mdl, "fitted");
840+ % ! plotDiagnostics (mdl);
841+ % ! plotSlice (mdl);
842+ % ! unwind_protect_cleanup
843+ % ! close (hf);
844+ % ! end_unwind_protect
845+
846+ ## Test input validation
847+ % !error<DATA, RESP, MODELFUN, and BETA0 are required> NonLinearModel (1)
848+ % !error<MODELFUN must be a function handle.> ...
849+ % ! NonLinearModel ([1; 2], [1; 2], "bad", [1])
850+ % !error<NonLinearModel: \(\) indexing is not supported> ...
851+ % ! mdl = fitnlm ([1;2;3;4], [1;2;3;4], @(b, x) b(1) * x, 1); mdl(1);
0 commit comments