classdef (InferiorClasses = {?matlab.graphics.axis.Axes, ?matlab.ui.control.UIAxes}) ClassificationGAM  < ...
        classreg.learning.classif.FullClassificationModel & ...
        classreg.learning.classif.CompactClassificationGAM
    %ClassificationGAM Generalized Additive Model (GAM) for classification.
    %   ClassificationGAM is an GAM model for classification with two
    %   classes. This model can predict response for new data. This model also
    %   stores data used for training and can compute resubstitution
    %   predictions.
    %
    %   Use FITCGAM to create a ClassificationGAM object by fitting a GAM
    %   model to training data.
    %
    %   This class is derived from CompactClassificationGAM.
    %
    %   ClassificationGAM properties:
    %       NumObservations             - Number of observations.
    %       X                           - Matrix or table of predictors used to train this model.
    %       Y                           - True class labels used to train this model.
    %       W                           - Weights of observations used to train this model.
    %       ModelParameters             - GAM parameters.
    %       PredictorNames              - Names of predictors used for this model.
    %       ExpandedPredictorNames      - Names of expanded predictors.
    %       ResponseName                - Name of the response variable.
    %       ClassNames                  - Names of classes in Y.
    %       Cost                        - Misclassification costs.
    %       Prior                       - Prior class probabilities.
    %       ScoreTransform              - Transformation applied to predicted classification scores.
    %       BinEdges                    - Bin edges for predictors.
    %       Intercept                   - Intercept of the GAM model.
    %       Interactions                - Pairs of predictors used to train the model.
    %       PairDetectionBinEdges       - Bin Edges used for detection of pairs.
    %       ReasonForTermination        - Reason for stopping GAM training.
    %       RowsUsed                    - Logical index for rows used in fit.
    %       HyperparameterOptimizationResults - An object or table describing the results of hyperparameter optimization.
    %
    %   ClassificationGAM methods:
    %       addInteractions       - Add interactions to univariate GAM.
    %       compact               - Compact this model.
    %       compareHoldout        - Compare two models using test data.
    %       crossval              - Cross-validate this model.
    %       edge                  - Classification edge.
    %       loss                  - Classification loss.
    %       margin                - Classification margins.
    %       partialDependence     - Partial Dependence values.
    %       plotLocalEffects      - Plot local effects.
    %       plotPartialDependence - Plot Partial Dependence.
    %       predict               - Predicted response of this model.
    %       resubEdge             - Resubstitution classification edge.
    %       resubLoss             - Resubstitution classification loss.
    %       resubMargin           - Resubstitution classification margins.
    %       resubPredict          - Resubstitution predicted response.
    %       resume                - Resume training of GAM.
    %
    %       Example: Train a GAM model on ionosphere data.
    %       load ionosphere
    %       gam = fitcgam(X,Y,'Interactions',5)
    %
    %   See also fitcgam, classreg.learning.classif.CompactClassificationGAM.
    
    %   Copyright 2020-2024 The MathWorks, Inc.
    
    properties(GetAccess=public,SetAccess=protected,Dependent=true)
        %PairDetectionBinEdges Bin Edges used for detection of predictor pairs.
        %   PairDetectionBinEdges is a vector with elements equal to the
        %   number of predictors. For categorical predictors, elements of
        %   the cell array are empty.
        %
        %   See also ClassificationGAM
        PairDetectionBinEdges;
        
        %ReasonForTermination Reason for stopping GAM training.
        %   ReasonForTermination is a structure containing reason for
        %   termination of training predictor trees and interaction trees.
        %
        %   See also ClassificationGAM
        ReasonForTermination
    end
    
    properties(GetAccess=protected,SetAccess=protected,Hidden=true,Dependent=true)
        %PrivResiduals Residuals from the last iteration of training gam.
        %   PrivResiduals is a cell array with elements equal to the
        %   number of observations.
        %
        %   See also ClassificationGAM
        PrivResiduals
        
        %PrivTerminationFlag Flag for reason for termination.
        %
        %   See also ClassificationGAM
        PrivTerminationFlag
    end
    
    methods
        function a = get.PairDetectionBinEdges(this)
            a = this.Impl.PairDetectionBinEdges;
        end
        function a = get.ReasonForTermination(this)
            a = this.Impl.ReasonForTermination;
        end
        function a = get.PrivResiduals(this)
            a = this.Impl.PrivResiduals;
        end
        function a = get.PrivTerminationFlag(this)
            a = this.Impl.PrivTerminationFlag;
        end
    end
    
    methods(Hidden)
        function this = ClassificationGAM(X,Y,W,modelParams,...
                dataSummary,classSummary,scoreTransform)
            % Protect against being called by the user
            if nargin~=7 || ischar(W)
                error(message('stats:ClassificationGAM:ClassificationGAM:DoNotUseConstructor'))
            end
            
            % Base constructor
            this = this@classreg.learning.classif.FullClassificationModel(...
                X,Y,W,modelParams,dataSummary,classSummary,scoreTransform);
            this = this@classreg.learning.classif.CompactClassificationGAM(...
                dataSummary,classSummary,scoreTransform,[]);
            dataSummary.BinnedX = [];
            this = fitGam(this);
        end
    end
    
    methods(Access = protected)
        function this = fitGam(this)
            
            if numel(this.ClassSummary.NonzeroProbClasses) < 2
                % Do not create any trees
                y = ismember(this.PrivY,this.ClassSummary.ClassNames(1));
                this.ModelParams.NumTreesPerPredictor   = 0;
                this.ModelParams.NumTreesPerInteraction = 0;
                doclass = 1; % One Class classification
            else
                y = ismember(this.PrivY,this.ClassSummary.NonzeroProbClasses(2));
                doclass = 2; % Two Class Classification
            end
            
            % nprint based on VerbosityLevel
            if this.ModelParams.VerbosityLevel <= 0
                nprint = 0;
            elseif this.ModelParams.VerbosityLevel == 2
                nprint = 1;
            else
                nprint = this.ModelParams.NumPrint;
            end
            
            this.Impl = classreg.learning.impl.GAMImpl.make(...
                this.PrivX,...
                y,...
                this.W * numel(y),...
                doclass,...
                this.DataSummary,...
                nprint,...
                this.ModelParams.MaxPValue,...
                this.ModelParams.InitialLearnRateForPredictors,...
                this.ModelParams.InitialLearnRateForInteractions,...
                this.ModelParams.NumTreesPerPredictor,...
                this.ModelParams.NumTreesPerInteraction,...
                this.ModelParams.MaxNumSplitsPerPredictor,...
                this.ModelParams.MaxNumSplitsPerInteraction,...
                this.ModelParams.Interactions,...
                this.ModelParams.VerbosityLevel,...
                false); % Fit Prediction Intervals
        end
        
        function s = propsForDisp(this,s)
            s = propsForDisp@classreg.learning.classif.CompactClassificationGAM(this,s);
            s = propsForDisp@classreg.learning.classif.FullClassificationModel(this,s);
        end
    end
    
    methods(Static,Hidden)
        function this = fit(X,Y,varargin)
            args = {'cost'                   'scoretransform'};
            defs = {    [] @classreg.learning.transform.logit};
            [cost,st,~,varargin] = internal.stats.parseArgs(args,defs,varargin{:});
            
            temp = ClassificationGAM.template(varargin{:});
            this = fit(temp,X,Y);
            
            this.ScoreTransform = st;
            this.Cost = cost;
        end
        
        function temp = template(varargin)
            classreg.learning.FitTemplate.catchType(varargin{:});
            temp = classreg.learning.FitTemplate.make('GAM','type','classification',varargin{:});
        end
        
        function [X,Y,W,dataSummary,classSummary,scoreTransform] = ...
                prepareData(X,Y,varargin)
            
            % Separate Interaction Terms from Formula
            [InteractionTerms,Y] = classreg.learning.impl.GAMImpl.separateInteractionTerms(X,Y);
            
            % Process input args - Set default for GAM
            args = {'NumBins'};
            defs = {      256};
            [nbins,~,varargin] = internal.stats.parseArgs(args,defs,varargin{:});
            
            [X,Y,W,dataSummary,classSummary,scoreTransform] = ...
                ClassificationTree.prepareData(X,Y,'NumBins',nbins,varargin{:});
            
            % Store Interaction Terms
            dataSummary.InteractionTerms = logical(InteractionTerms);
            
            % Only binary classification GAM is allowed
            classnames = classSummary.ClassNames;
            
            if numel(classnames)>2
                error(message('stats:ClassificationGAM:prepareData:BadNumClasses'))
            end
            
        end
        
        function [isok,interactions] = checkInteractions(interactions,P)
            if islogical(interactions)
                % remove duplicate rows
                interactions = unique(interactions,'rows','stable');
                [t,d] = size(interactions);
                if(d ~= P)
                    error(message('stats:classreg:learning:modelparams:GAMParams:fillDefaultParams:BadInteractionsColumn',P))
                elseif(~(t <= P*(P-1)/2))
                    error(message('stats:classreg:learning:modelparams:GAMParams:fillDefaultParams:BadInteractionsRows',P*(P-1)/2))
                elseif any(sum(interactions,2) ~= 2)
                    error(message('stats:classreg:learning:modelparams:GAMParams:fillDefaultParams:BadInteractionTerms'))
                else
                    isok = true;
                end
            elseif isnumeric(interactions)
                isok = classreg.learning.modelparams.GAMParams.isNumericRealScalarNoNaNInf(interactions,1);
                isok = isok && ~(interactions<=0); % interactions cannot be 0 in addInteractions
                % max numeric value of interactions is P*(P-1)/2
                if interactions > P*(P-1)/2
                    error(message('stats:classreg:learning:modelparams:GAMParams:fillDefaultParams:BadInteractionsRows',P*(P-1)/2))
                end
            elseif ischar(interactions)
                allowedVals = {'all'};
                isMatch = strncmpi(interactions,allowedVals,length(interactions));
                if isMatch == 1
                    isok = true;
                    interactions = allowedVals{isMatch};
                else
                    % No match.
                    isok = false;
                end
            else
                isok = false;
            end
        end
        
        function checkNameValuePairs(nprint,verbose,maxpval,ntrees,nsplits,learnRate)
            % Helper function to validate Name Value pairs passed to methods of
            % ClassificationGAM and RegressionGAM
            import classreg.learning.modelparams.GAMParams
            
            % Check NumPrint
            if ~isempty(nprint)
                isok = GAMParams.isNumericRealScalarNoNaNInf(nprint,1);
                isok = isok && ~(nprint<0);
                if ~isok
                    error(message('stats:classreg:learning:modelparams:GAMParams:make:BadNumPrint'))
                end
            end
            
            % Check Verbose
            if ~isempty(verbose)
                isok = GAMParams.isNumericRealScalarNoNaNInf(verbose,1);
                isok = isok && ~(verbose<0) && ~(verbose>2);
                if ~isok
                    error(message('stats:classreg:learning:modelparams:GAMParams:make:BadVerbose'))
                end
            end
            
            % Check MaxPValue
            if ~isempty(maxpval)
                isok = GAMParams.isNumericRealScalarNoNaNInf(maxpval,0);
                isok = isok && ~(maxpval<0) && ~(maxpval>1);
                if ~isok
                    error(message('stats:classreg:learning:modelparams:GAMParams:make:BadMaxPValue'))
                end
            end
            
            % Check InitialLearnRateForInteractions
            if ~isempty(learnRate)
                isok = GAMParams.isNumericRealScalarNoNaNInf(learnRate,0);
                isok = isok && ~(learnRate<=0) && ~(learnRate>1);
                if ~isok
                    error(message('stats:classreg:learning:modelparams:GAMParams:make:BadInitialLearnRateForInteractions'))
                end
            end
            
            % Check NumTreesPerInteraction
            if ~isempty(ntrees)
                isok = GAMParams.isNumericRealScalarNoNaNInf(ntrees,1);
                % ntreesperinteraction cannot take value 0
                isok = isok && ~(ntrees<=0);
                if ~isok
                    error(message('stats:classreg:learning:modelparams:GAMParams:make:BadNumTreesPerInteraction'))
                end
            end
            
            % Check MaxNumSplitsPerInteraction
            if ~isempty(nsplits)
                isok = GAMParams.isNumericRealScalarNoNaNInf(nsplits,1);
                isok = isok && ~(nsplits<=0);
                if ~isok
                    error(message('stats:classreg:learning:modelparams:GAMParams:make:BadMaxNumSplitsPerInteraction'))
                end
            end
        end
    end
    
    methods
        function cmp = compact(this,varargin)
            %COMPACT Compact GAM.
            %   CMP = COMPACT(MDL) returns an object of class
            %   CompactClassificationGAM holding the structure of the trained
            %   GAM. The compact object does not contain X and Y used for training.
            %
            %   See also ClassificationGAM,
            %   classreg.learning.classif.CompactClassificationGAM..
            
            dataSummary = this.DataSummary;
            dataSummary.RowsUsed = [];
            dataSummary.BinnedX = [];
            dataSummary.BinEdges = {};
            
            compactImpl = compact(this.Impl);
            cmp = classreg.learning.classif.CompactClassificationGAM(...
                dataSummary,this.ClassSummary,...
                this.PrivScoreTransform,compactImpl);
        end
        
        function [varargout] = resubPredict(this,varargin)
            %RESUBPREDICT Predict resubstitution response of GAM.
            %   [LABEL,SCORE] = RESUBPREDICT(MDL) returns predicted class labels and
            %   scores for GAM model MDL and training data MDL.X. Classification labels
            %   LABEL have the same type as Y used for training. Scores SCORE are an
            %   N-by-2 numeric matrix for N observations and 2 classes. The predicted label
            %   is assigned to the class with the minimal misclassification cost.
            %
            %   [LABEL,SCORE] = RESUBPREDICT(MDL,'Name1',Value1,'Name2',Value2,...) specifies
            %   optional parameter name/value pairs:
            %       'IncludeInteractions'   - Boolean value to indicate if
            %                                 interactions should be considered
            %                                 when applying the method on a GAM
            %                                 model trained with interactions.
            %                                 Value cannot be true for GAM model
            %                                 trained without interactions.
            %                                 Default: false for MDL
            %                                 without interaction terms and
            %                                 true for MDL with
            %                                 interaction terms.
            %
            %   See also ClassificationGAM, predict.
            
            [varargin{:}] = convertStringsToChars(varargin{:});
            [varargout{1:nargout}] = ...
                resubPredict@classreg.learning.classif.FullClassificationModel(this,varargin{:});
        end
        
        function [varargout] = resubLoss(this,varargin)
            %RESUBLOSS Classification cost by resubstitution.
            %   L = RESUBLOSS(MDL) returns resubstitution classification cost for model
            %   MDL.
            %
            %   L = RESUBLOSS(MDL,'Name1',Value1,'Name2',Value2,...) specifies optional
            %   parameter name/value pairs:
            %       'lossfun'               - Function handle for loss, or string
            %                                 representing a built-in loss function.
            %                                 Available loss functions for
            %                                 classification: 'binodeviance',
            %                                 'classiferror', 'mincost', 'classifcost',
            %                                 'exponential', and 'hinge'. If you pass a
            %                                 function handle FUN, RESUBLOSS calls it
            %                                 as shown below:
            %                                   FUN(C,S,W,COST)
            %                                 where C is an N-by-K logical matrix for N
            %                                 rows in X and K classes in the ClassNames
            %                                 property, S is an N-by-K numeric matrix,
            %                                 W is a numeric vector with N elements,
            %                                 and COST is a K-by-K numeric matrix. C
            %                                 has one true per row for the true class.
            %                                 S is a matrix of posterior probabilities
            %                                 for classes with one row per observation,
            %                                 similar to SCORE output from PREDICT. W
            %                                 is a vector of observation weights. COST
            %                                 is a matrix of misclassification costs.
            %                                 Default: 'mincost' if ScoreTransform is
            %                                 'logit' and 'classiferror' otherwise
            %       'IncludeInteractions'   - Boolean value to indicate if interactions
            %                                 should be considered when applying the
            %                                 method on a GAM  model trained with interactions.
            %                                 Value cannot be true for GAM model
            %                                 trained without interactions.
            %                                 Default: false for MDL without
            %                                 interaction terms and true
            %                                 for MDL with interaction terms.
            %
            %   See also fitcgam, ClassificationGAM, ClassNames, predict, loss.
            
            [varargin{:}] = convertStringsToChars(varargin{:});
            [varargout{1:nargout}] = ...
                resubLoss@classreg.learning.classif.FullClassificationModel(this,varargin{:});
        end
        
        function m = resubMargin(this,varargin)
            %RESUBMARGIN Classification margins by resubstitution.
            %   M = RESUBMARGIN(MDL) returns resubstitution classification margins for
            %   model MDL. Classification margin is the difference between
            %   classification score for the true class and maximal classification
            %   score for the false classes. The returned M is a numeric column vector
            %   of length size(MDL.X,1).
            %
            %   M = RESUBMARGIN(MDL,'Name1',Value1,'Name2',Value2,...) specifies
            %   optional parameter name/value pairs:
            %       'IncludeInteractions'   - Boolean value to indicate if
            %                                 interactions should be considered
            %                                 when applying the method on a GAM
            %                                 model trained with interactions.
            %                                 Value cannot be true for GAM model
            %                                 trained without interactions.
            %                                 Default: false for MDL without
            %                                 interaction terms and true
            %                                 for MDL with interaction terms.
            %
            %   See also ClassificationGAM, margin.
            
            [varargin{:}] = convertStringsToChars(varargin{:});
            classreg.learning.FullClassificationRegressionModel.catchWeights(varargin{:});
            m = margin(this,this.X,this.Y,varargin{:});
        end
        
        function e = resubEdge(this,varargin)
            %RESUBEDGE Classification edge by resubstitution.
            %   E = RESUBEDGE(MDL) returns classification edge obtained by model MDL for
            %   training data MDL.X and MDL.Y. Classification edge is classification
            %   margin averaged over the entire data.
            %
            %   E = RESUBEDGE(MDL,'Name1',Value1,'Name2',Value2,...) specifies
            %   optional parameter name/value pairs:
            %       'IncludeInteractions'   - Boolean value to indicate if
            %                                 interactions should be considered
            %                                 when applying the method on a GAM
            %                                 model trained with interactions.
            %                                 Value cannot be true for GAM model
            %                                 trained without interactions.
            %                                 Default: false for MDL without
            %                                 interaction terms and true
            %                                 for MDL with interaction terms.
            %
            %   See also ClassificationGAM, resubMargin, edge
            
            [varargin{:}] = convertStringsToChars(varargin{:});
            classreg.learning.FullClassificationRegressionModel.catchWeights(varargin{:});
            e = edge(this,this.X,this.Y,'Weights',this.W,varargin{:});
        end
        
        function this = resume(this,numtrees,varargin)
            %RESUME Resume training of GAM.
            %   NEWMDL = RESUME(MDL,NUMTREES) trains model MDL for NUMTREES more iterations.
            %   NUMTREES must be a positive integer scalar.
            %
            %   NEWMDL = RESUME(MDL,NUMTREES,'Name1',Value1,'Name2',Value2,...) specifies optional
            %   parameter name/value pairs:
            %   optional parameter name/value pairs:
            %       'Verbose'       - Verbosity level, one of: 0, 1, or 2. Default:
            %                         Value passed to FITCGAM.
            %       'NumPrint'      - Number of iterations between consecutive
            %                         diagnostic print-outs, a non-negative integer.
            %                         RESUME uses this parameter only if value
            %                         for the 'Verbose' parameter is 1.
            %                         Default: Value passed to FITCGAM.
            %
            % See also ClassificationGAM, fitcgam.
            
            [varargin{:}] = convertStringsToChars(varargin{:});
            
            args = {                'NumPrint',                        'Verbose'};
            defs = { this.ModelParams.NumPrint,  this.ModelParams.VerbosityLevel};
            
            [nprint,verbose] = internal.stats.parseArgs(args,defs,varargin{:});
            
            import classreg.learning.modelparams.GAMParams
            
            % Check NumTrees
            isok = GAMParams.isNumericRealScalarNoNaNInf(numtrees,1);
            isok = isok && ~(numtrees<=0) && ~isempty(numtrees);
            if ~isok
                error(message('stats:classreg:learning:impl:GAMImpl:resume:BadNumTrees'))
            else
                numtrees = ceil(numtrees);
            end
            
            if islogical(verbose)
                verbose = double(verbose);
            end
            % Check NumPrint and Verbose
            ClassificationGAM.checkNameValuePairs(nprint,verbose,[],[],[],[]);
            
            if verbose <= 0
                nprint = 0;
            elseif verbose == 2
                nprint = 1;
            end
            
            % Update ModelParams
            this.ModelParams.NumPrint       = nprint;
            this.ModelParams.VerbosityLevel = verbose;
            
            if numel(this.ClassSummary.NonzeroProbClasses) < 2
                doclass = 1;  % One Class Classification
                numtrees = 0; % Do not train any trees
            else
                doclass = 2;  % Two Class Classification
            end
            
            % Check model type - set NumTreesPer* and get MaxNumSplitsPer* ,
            % InitialLearnRateFor*
            if isempty(this.TreesPerInteraction)
                this.ModelParams.NumTreesPerPredictor = this.ModelParams.NumTreesPerPredictor + ...
                    numtrees;
                nsplits   = this.ModelParams.MaxNumSplitsPerPredictor;
                learnRate = this.ModelParameters.InitialLearnRateForPredictors;
                do1d = true;
            else
                this.ModelParams.NumTreesPerInteraction = this.ModelParams.NumTreesPerInteraction + ...
                    numtrees;
                nsplits   = this.ModelParams.MaxNumSplitsPerInteraction;
                learnRate = this.ModelParameters.InitialLearnRateForInteractions;
                do1d = false;
            end
            
            classnames = this.ClassSummary.ClassNames;
            
            % Call resume from GAMImpl
            this.Impl = resume(...
                this.Impl,...
                this.PrivX,...
                this.PrivY,...
                this.W * numel(this.PrivY),...
                doclass,...
                classnames,...
                learnRate,...
                numtrees,...
                nsplits,...
                verbose,...
                nprint,...
                this.DataSummary,...
                do1d);
            
        end
        
        function this = addInteractions(this,interactions,varargin)
            %ADDINTERACTIONS Add Interactions to Univariate GAM Model.
            %   NEWMDL = ADDINTERACTIONS(MDL,INTERACTIONS) trains model MDL with INTERACTIONS.
            %   INTERACTIONS must be specified as one of:
            %                          * A string:
            %                            - 'all' : P*(P-1)/2 interactions for P predictors.
            %                          * A scalar positive integer: Number of interactions.
            %                          * Logical term matrix specifying interaction pairs.
            %                             A T X P logical matrix with each row identifying
            %                             the pair where P is the number of predictors.
            %
            %   NEWMDL = ADDINTERACTIONS(MDL,INTERACTIONS,'Name1',Value1,'Name2',Value2,...) specifies optional
            %   parameter name/value pairs:
            %       'InitialLearnRateForInteractions' - Learning rate for boosting interaction trees.
            %                        Scalar value between 0 and 1. Default: 1
            %       'MaxPValue'    - Threshold p-value for detection of interaction pairs.
            %                        Scalar value between 0 and 1.
            %                        Default: 1 (all interactions are used)
            %       'MaxNumSplitsPerInteraction' - Maximal Number of splits per interaction
            %                        pair. A positive integer scalar applied to all interactions.
            %                        Default: 4
            %       'NumTreesPerInteraction' - Number of trees trained per interaction pair.
            %                        A positive integer scalar applied to all interactions.
            %                        Default: 100
            %       'Verbose'       - Verbosity level, one of: 0, 1, or 2. Default:
            %                         Value passed to FITCGAM.
            %       'NumPrint'      - Number of iterations between consecutive
            %                         diagnostic print-outs, a non-negative integer.
            %                         ADDINTERACTIONS uses this parameter only if value
            %                         for the 'Verbose' parameter is 1.
            %                         Default: Value passed to FITCGAM.
            %
            % See also ClassificationGAM, fitcgam.
            
            [varargin{:}] = convertStringsToChars(varargin{:});
            interactions  = convertStringsToChars(interactions);
            
            % If model is not univariate cannot add interactions
            if ~isempty(this.TreesPerInteraction)
                error(message('stats:ClassificationGAM:addInteractions:BadModel'))
            end
            
            % Number of Predictors
            P = size(this.X,2);
            
            % Check interactions
            [isok,interactions] =  ClassificationGAM.checkInteractions(interactions,P);
            
            if ~isok
                error(message('stats:ClassificationGAM:addInteractions:BadInteractions'))
            end
            
            % Parse Name-Value Pairs
            args = {'maxpvalue' 'numtreesperinteraction' 'maxnumsplitsperinteraction'...
                'verbose'               'numprint' 'initiallearnrateforinteractions'};
            defs = {          1                      100                            4,...
                this.ModelParams.VerbosityLevel this.ModelParams.NumPrint                1};
            
            [maxpval,ntrees,nsplits,verbose,nprint,learnRate] = ...
                internal.stats.parseArgs(args,defs,varargin{:});
            
            if islogical(verbose)
                verbose = double(verbose);
            end
            
            % Validate Name-Value Pairs
            ClassificationGAM.checkNameValuePairs(nprint,verbose,maxpval,ntrees,nsplits,learnRate);
            
            if verbose <= 0
                nprint = 0;
            elseif verbose == 2
                nprint = 1;
            end
            
            if numel(this.ClassSummary.NonzeroProbClasses) < 2
                doclass = 1; % One Class Classification
                ntrees = 0;  % Do not train any trees
            else
                doclass = 2; % Two Class Classification
            end
            
            % Update ModelParams
            this.ModelParams.NumPrint                        = nprint;
            this.ModelParams.VerbosityLevel                  = verbose;
            this.ModelParams.MaxNumSplitsPerInteraction      = nsplits;
            this.ModelParams.NumTreesPerInteraction          = ntrees;
            this.ModelParams.MaxPValue                       = maxpval;
            this.ModelParams.InitialLearnRateForInteractions = learnRate;
            
            classnames = this.ClassSummary.ClassNames;
            
            % Call addInteractions from GAMImpl
            this.Impl = addInteractions(...
                this.Impl,...
                this.PrivX,...
                this.PrivY,...
                this.W * numel(this.PrivY),...
                doclass,...
                classnames,...
                learnRate,...
                ntrees,...
                nsplits,...
                verbose,...
                nprint,...
                this.DataSummary,...
                interactions,...
                maxpval);
        end
        
        function [AX] = plotPartialDependence(this,features,labels,varargin)
            %PLOTPARTIALDEPENDENCE Partial Dependence Plot for 1-D or 2-D visualization
            %   plotPartialDependence(MDL,VARS,LABELS) plots the partial dependence
            %   PD between the predictor variables listed in VARS and the scores for
            %   the classes specified in LABELS by using the classification model MDL
            %   and by averaging over the data used in fitting the model. You can
            %   specify one or two variables using VARS. VARS is either column indices
            %   of predictors or names of predictors. The names must match the entries
            %   in MDL.PredictorNames. You can specify one or multiple classes using
            %   LABELS. The values in LABELS must match and have the same data type as
            %   the class names in MDL.ClassNames. LABELS must be a single label if
            %   VARS specifies two predictors.
            %
            %   plotPartialDependence(...,DATA) specifies the data to be used for
            %   averaging. DATA is a matrix or table of data to be used in place of the
            %   data used in fitting the model.
            %
            %   plotPartialDependence(...,Name,Value) specifies additional options using
            %   one or more name-value pair arguments:
            %
            %   'Conditional'               'none' (default) instructs the method to create
            %                               a partial dependence plot with no conditioning,
            %                               'absolute' to create an individual conditional
            %                               expectation (ICE) plot, and 'centered' to
            %                               create an ICE plot with centered data.
            %
            %   'NumObservationsToSample'   An integer specifying the number
            %                               of rows to sample at random from the dataset
            %                               (either the data specified by the argument DATA
            %                               or the data used to train MDL). The default
            %                               is to use all rows.
            %
            %   'QueryPoints'               The points at which to calculate the
            %                               partial dependence. With one predictor, the
            %                               value of 'QueryPoints' must be a column
            %                               vector. With two predictors, the value of
            %                               'QueryPoints' must be a 1x2 cell array
            %                               containing a separate vector for each
            %                               predictor. The default is to use 100 points
            %                               equally spaced across the range of the
            %                               predictor.
            %
            %   'UseParallel'               Can be true to instruct the method to perform
            %                               the averaging calculations in parallel (using
            %                               parfor), or false (default) to instruct the
            %                               method not to use parallel computations.
            %
            % 	'Parent'                    Instructs the method to create a partial
            %                               dependence plot using the axes with handle
            %                               specified by the value of this parameter.
            %
            %   'IncludeIntercept'          Can be true to include the intercept value in
            %                               the partial dependence computation,
            %                               or false to not use the intercept
            %                               term. Default: true
            %
            %   'IncludeInteractions'       Can be true to include the interactions in the
            %                               partial dependence computation for a GAM
            %                               model trained with interactions, or false to exclude
            %                               interaction terms from the computation of
            %                               partial dependence.
            %                               Default: false for MDL without
            %                               interaction terms and true
            %                               for MDL with interaction terms.
            %
            %   AX = plotPartialDependence(...) returns a handle AX to the axes of the
            %   plot.
            %
            %   Examples:
            %       % Partial Dependence Plot of Classification GAM
            %       load census1994
            %       X = adultdata(:,{'age','workClass','education_num','marital_status','race',...
            %           'sex','capital_gain','capital_loss','hours_per_week','salary'});
            %       f = fitcgam(X,'salary');
            %
            %       plotPartialDependence(f,'age','<=50K');
            %       plotPartialDependence(f,{'age','workClass'},'>50K');
            %       plotPartialDependence(f,'age', {'<=50K' '>50K'})
            %
            %       % Obtain optional output Axes handle
            %       ax = plotPartialDependence(f,'age','<=50K');
            %
            %       % With additional Data
            %       plotPartialDependence(f,'age','<=50K',X);
            %       plotPartialDependence(f,1,'<=50K',X);
            %
            %       % With optional name-value pairs
            %       plotPartialDependence(f,1,'<=50K',X,'NumObservationsToSample',100);
            %       plotPartialDependence(f,1,'<=50K',X,'UseParallel',true);
            %       plotPartialDependence(f,1,'<=50K',X,'UseParallel',true,'Conditional','none');
            %
            %       % Plot the Individual Conditional Expectation
            %       plotPartialDependence(f,1,'<=50K',X,'Conditional','absolute');
            %
            %       % Provide alternative query points
            %       xi = linspace(min(X{:,'age'}),min(X{:,'age'}))';
            %       plotPartialDependence(f,1,'<=50K','QueryPoints',xi);
            %
            %       xi = cell(1,2);
            %       xi{1} = linspace(min(X{:,1}),max(X{:,1}))';
            %       xi{2} = linspace(min(X{:,9}),max(X{:,9}))';
            %       plotPartialDependence(f,[1,9],'<=50K','QueryPoints',xi);
            %
            %   See also ClassificationGAM
            
            features = convertStringsToChars(features);
            [varargin{:}] = convertStringsToChars(varargin{:});
            
            % Default values for include interactions
            if isempty(this.TreesPerInteraction)
                defaultui = false;
            else
                defaultui = true;
            end
            
            % Check inputs with inputParser. This step ensures that the
            % fourth argument is either a Name-Value pair or data, no other strings/char
            % array allowed.
            p = inputParser;
            addRequired(p,'Model');
            addRequired(p,'Var');
            addRequired(p,'Label');
            addOptional(p,'Data',this.X); % Default - training data
            addParameter(p,'Conditional',{'none' 'absolute' 'centered'});
            addParameter(p,'NumObservationsToSample',0);
            addParameter(p,'QueryPoints',[]);
            addParameter(p,'UseParallel',false);
            addParameter(p,'ParentAxisHandle',[]);
            addParameter(p,'IncludeInteractions',defaultui);
            addParameter(p,'IncludeIntercept',true);
            % Allowing additional Name-Value pairs specific to custom models
            % here so that a more specific error message can be thrown when parsing.
            addParameter(p,'CategoricalPredictors',[]);
            addParameter(p,'OutputColumns',[]);

            parse(p,this,features,labels,varargin{:});
            X = p.Results.Data;
            incinteractions = p.Results.IncludeInteractions;
            incintercept = p.Results.IncludeIntercept;
            
            % Classification flag is true
            classif = true;
            
            %------Parse Data-----------------
            % If third argument is a char, its a parameter name else it is Data
            if(nargin>3 && ~ischar(varargin{1}))
                % Pass everything but the first argument(Data)to compact method
                varargin = varargin(2:end);
            end
            
            % Validate GAM Name Value Pairs
            internal.stats.parseOnOff(incinteractions,'IncludeInteractions');
            internal.stats.parseOnOff(incintercept,'IncludeIntercept');
            
            if ((incinteractions) && isempty(this.TreesPerInteraction))
                error(message('stats:classreg:learning:impl:CompactGAMImpl:parseIncludeArgs:BadIncludeInteractions'))
            end
            
            % Call the function from responsible.impl
            ax = responsible.impl.plotPartialDependence(this,features,...
                labels,X,classif,varargin{:});
            if(nargout > 0)
                AX = ax;
            end
        end
        
        function [pd,varargout] = partialDependence(this,features,labels,varargin)
            %PARTIALDEPENDENCE Partial Dependence for one and two variables
            %   PD = partialDependence(MDL,VARS,LABELS) computes the partial
            %   dependence PD between the predictor variables listed in VARS and the
            %   scores for the classes specified in LABELS by using the classification
            %   model MDL and by averaging over the data used in training the model.
            %   You can specify one or two variables using VARS. VARS is either column
            %   indices of predictors or names of predictors. The names must match the
            %   entries in MDL.PredictorNames. You can specify one or multiple
            %   classes using LABELS. The values in LABELS must match and have the same
            %   data type as the class names in MDL.ClassNames. If there are multiple
            %   values in both VARS and LABELS, then PD is a three-dimensional array.
            %
            %   PD = partialDependence(...,DATA) specifies the data to be used for
            %   averaging. DATA is a matrix or table of data to be used in place of the
            %   data used in fitting the model.
            %
            %   PD = partialDependence(...,Name,Value) specifies additional options
            %   using one or more name-value pair arguments:
            %
            %       'NumObservationsToSample':      An integer specifying the number
            %                                       of rows to sample at random from the
            %                                       dataset (either the data specified by
            %                                       argument DATA or the data used to train
            %                                       MDL). The default is to use all rows.
            %
            %       'QueryPoints':                  The points at which to calculate
            %                                       the partial dependence. For the case of
            %                                       a single predictor, the value of
            %                                       'QueryPoints' must be a column vector.
            %                                       For the case of two predictors, the
            %                                       value of 'QueryPoints' must be a 1x2
            %                                       cell array containing a separate vector
            %                                       for each predictor. The default is to
            %                                       use 100 points equally spaced across
            %                                       the range of the predictor.
            %
            %       'UseParallel'                   Can be true to instruct the method to
            %                                       perform the averaging calculations in
            %                                       parallel (using parfor), or false
            %                                       (default) to instruct the method not to
            %                                       use parallel computation.
            %
            %   'IncludeIntercept'                  Can be true to include the intercept value in
            %                                       the partial dependence computation,
            %                                       or false to not use the intercept
            %                                       term. Default: true
            %
            %   'IncludeInteractions'               Can be true to include the interactions in the
            %                                       partial dependence computation for a GAM
            %                                       model trained with interactions, or false to exclude
            %                                       interaction terms from the computation of
            %                                       partial dependence
            %                                       Default: false for MDL without
            %                                       interaction terms and true
            %                                       for MDL with interaction terms.
            %
            %   [PD,X,Y] = partialDependence(...) also returns the arrays X and Y
            %   containing the query point values of the first and second predictor in VARS,
            %   respectively.
            %
            %   Examples:
            %       % Partial Dependence of Classification GAM
            %       load census1994
            %       X = adultdata(:,{'age','workClass','education_num','marital_status','race',...
            %           'sex','capital_gain','capital_loss','hours_per_week','salary'});
            %       f = fitcgam(X,'salary');
            %
            %       pd = partialDependence(f,'age','<=50K');
            %
            %       % Obtain values x of the variable 'age'
            %       [pd,x] = partialDependence(f,'age',{'<=50K','>50K'});
            %
            %       % plot partial dependence of the first and second classes scores on
            %       % 'age'
            %       plot(x,pd)
            %
            %       % Obtain values x and y of variables 'age' and 'workClass'
            %       [pd,x,y] = partialDependence(f,{'age','workClass'},{'<=50K','>50K'});
            %
            %       % plot partial dependence for class '<=50K' on variables
            %       % 'age' and 'workClass'
            %       surf(x,y,squeeze(pd(1,:,:)))
            %
            %       % plot partial dependence for class '>50K' on variables
            %       % 'age' and 'workClass'
            %       surf(x,y,squeeze(pd(2,:,:)))
            %
            %       % With additional Data
            %       pd = partialDependence(f,'age','<=50K',X);
            %       pd = partialDependence(f,1,'<=50K',X);
            %
            %       % With optional name-value pairs
            %       pd = partialDependence(f,1,'<=50K',X,'NumObservationsToSample',100);
            %       pd = partialDependence(f,1,'<=50K',X,'UseParallel',true);
            %
            %       % Provide alternative query points
            %       xi = linspace(min(X{:,'age'}),max(X{:,'age'}))';
            %       pd = partialDependence(f,1,'<=50K','QueryPoints',xi);
            %
            %       xi = cell(1,2);
            %       xi{1} = linspace(min(X{:,1}),max(X{:,1}))';
            %       xi{2} = linspace(min(X{:,9}),max(X{:,9}))';
            %       pd = partialDependence(f,[1,9],'<=50K','QueryPoints',xi);
            %
            %   See also ClassificationGAM
            
            features = convertStringsToChars(features);
            [varargin{:}] = convertStringsToChars(varargin{:});
            
            % Default values for include interactions
            if isempty(this.TreesPerInteraction)
                defaultui = false;
            else
                defaultui = true;
            end
            
            % Check inputs with inputParser. This step ensures that the
            % fourth argument is either a Name-Value pair or data, no other strings/char
            % array allowed.
            p = inputParser;
            addRequired(p,'Model');
            addRequired(p,'Var');
            addRequired(p,'Label');
            addOptional(p,'Data',this.X); % Default - training data
            addParameter(p,'NumObservationsToSample',0);
            addParameter(p,'QueryPoints',[]);
            addParameter(p,'UseParallel',false);
            addParameter(p,'IncludeInteractions',defaultui);
            addParameter(p,'IncludeIntercept',true);
            % Allowing additional Name-Value pairs specific to custom models
            % here so that a more specific error message can be thrown when parsing.
            addParameter(p,'CategoricalPredictors',[]);
            addParameter(p,'OutputColumns',[]);

            parse(p,this,features,labels,varargin{:});
            X = p.Results.Data;
            incinteractions = p.Results.IncludeInteractions;
            incintercept = p.Results.IncludeIntercept;
            
            % classification flag is true
            classif = true;
            
            %------Parse Data-----------------
            % If third argument is a char, its a parameter name else it is Data
            if(nargin>3 && ~ischar(varargin{1}))
                % Pass everything but the first argument (Data) to compact method
                varargin = varargin(2:end);
            end
            
            % Validate GAM Name Value Pairs
            internal.stats.parseOnOff(incinteractions,'IncludeInteractions');
            internal.stats.parseOnOff(incintercept,'IncludeIntercept');
            
            if ((incinteractions) && isempty(this.TreesPerInteraction))
                error(message('stats:classreg:learning:impl:CompactGAMImpl:parseIncludeArgs:BadIncludeInteractions'))
            end
            
            % Call the function from responsible.impl
            [pd,x,y] = responsible.impl.partialDependence(this,features,...
                labels,X,classif,varargin{:});
            varargout{1} = x;
            varargout{2} = y;
        end
        
        function partModel = crossval(this,varargin)
            %CROSSVAL Cross-validate this model.
            %   CVMODEL = CROSSVAL(MDL) builds a partitioned model CVMODEL from model
            %   MDL represented by a full object for classification. You can then
            %   assess the predictive performance of this model on cross-validated data
            %   using methods and properties of CVMODEL. By default, CVMODEL is built
            %   using 10-fold cross-validation on the training data.
            %
            %   CVMODEL = CROSSVAL(MDL,'PARAM1','Name1',Value1,'Name2',Value2,...) specifies
            %   optional parameter name/value pairs:
            %      'KFold'      - Number of folds for cross-validation, a numeric
            %                     positive scalar; 10 by default.
            %      'Holdout'    - Holdout validation uses the specified
            %                     fraction of the data for test, and uses the rest of
            %                     the data for training. Specify a numeric scalar
            %                     between 0 and 1.
            %      'Leaveout'   - If 'on', use leave-one-out cross-validation.
            %      'CVPartition' - An object of class CVPARTITION; empty by default. If
            %                      a CVPARTITION object is supplied, it is used for
            %                      splitting the data into subsets.
            %
            %   See also fitcgam, ClassificationGAM, cvpartition,
            %   classreg.learning.partition.ClassificationPartitionedGAM.
            
            % Set verbosity to 0 for cross validation.
            this.ModelParams.VerbosityLevel = 0;
            
            % Call superclass method.
            partModel = crossval@classreg.learning.classif.FullClassificationModel(this,varargin{:});
            
            partModel.ScoreTransform = this.PrivScoreTransform;
            partModel.Cost = this.Cost;
        end % end of crossval.
    end
end

% LocalWords:  gam PValue ntreesperinteraction NEWMDL ADDINTERACTIONS maxpvalue
% LocalWords:  numtreesperinteraction maxnumsplitsperinteraction initiallearnrateforinteractions
% LocalWords:  adultdata
