Skip to content

Commit b2c5129

Browse files
committed
NonLinearModel: add demo and comprehensive BIST coverage
The class shipped without any tests, demo, or input-validation checks; its behaviour was exercised only indirectly through fitnlm. Add a %!demo and 17 tests covering the object directly: * The coefficient table (estimate, SE, tStat) against MATLAB, with tStat cross-checked as estimate ./ SE. * Sums of squares (SSE, SST, SSR) and their definitions, MSE/RMSE/DFE, and the ordinary and adjusted R^2, both by value (MATLAB-verified) and by identity. * Log-likelihood and the AIC/BIC/AICc criteria, by value and by identity. * Count/size properties, default and custom coefficient names, a symmetric covariance with SE^2 on the diagonal, and raw residuals as response minus fit. * Methods: predict (fitted values and confidence bounds, MATLAB-verified), feval, coefCI (default and custom alpha), coefTest, random, disp output, chained subsref, table-input equivalence, and the residual/slice plots. * Input-validation error tests for the constructor. * inst/NonLinearModel.m: Add the demo and the tests above.
1 parent 8c6b978 commit b2c5129

1 file changed

Lines changed: 149 additions & 0 deletions

File tree

inst/NonLinearModel.m

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,3 +700,152 @@ function disp (this)
700700
out = b;
701701
endif
702702
endfunction
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

Comments
 (0)