classdef incrementalClassificationNaiveBayes < incremental.classif.ClassificationModel
    %incrementalClassificationNaiveBayes Naive Bayes classification model
    %   for incremental learning. This model can train with and predict responses 
    %   for new data.
    %
    %   incrementalClassificationNaiveBayes properties:
    %       Cost                                - Misclassification costs.
    %       CategoricalPredictors               - Indices of categorical predictors.
    %       CategoricalLevels                   - The levels of predictors that use the 'mvmn' distribution.
    %       DistributionNames                   - The distributions used to model each predictor.
    %       DistributionParameters              - Estimated parameters for the individual distributions.
    %       ClassNames                          - Names of classes in Y.
    %       Prior                               - Prior class probabilities.
    %       ScoreTransform                      - Transformation applied to predicted classification scores.
    %       NumPredictors                       - Number of predictors.
    %       NumTrainingObservations             - Number of observations used for training the incremental model.
    %       MetricsWarmupPeriod                 - Number of observations that are used for training
    %                                             prior to metrics tracking.
    %       MetricsWindowSize                   - Window size for window metrics tracking.
    %       IsWarm                              - Flag to indicate if incremental model is warm.
    %       Metrics                             - Metrics information.
    %
    %   incrementalClassificationNaiveBayes methods:
    %       incrementalClassificationNaiveBayes - Construct incremental naive Bayes 
    %                                             classification model.
    %       fit                                 - Train model with new data.     
    %       loss                                - Classification loss.
    %       perObservationLoss                  - Per observation classification loss.
    %       predict                             - Predict responses of the model.
    %       reset                               - Reset model.
    %       updateMetrics                       - Update metrics with new data.
    %       updateMetricsAndFit                 - Update metrics and train model with new data.
    %
    %
    %   Example 1: Initialize and train an incremental naive Bayes model without providing any
    %   information.
    %
    %       mdl  = incrementalClassificationNaiveBayes('MaxNumClasses',5);
    %       mdl  = updateMetricsAndFit(mdl,X,Y);
    %
    %   Example 2: Initialize an incremental naive Bayes model using a traditionally trained model
    %   and continue incremental training.
    %
    %       ttModel = fitcnb(Xoffline,Yoffline);
    %       mdl = incrementalLearner(ttModel);
    %       mdl = updateMetrics(mdl,Xnew,Ynew,'Weights',Wnew);
    %       mdl = fit(mdl,Xnew,Ynew,'Weights',Wnew);
    %
    %   See also ClassificationNaiveBayes.incrementalLearner.

    %   Copyright 2020-2023 The MathWorks, Inc.

    properties (Dependent, SetAccess = protected)
        %COST Cost Misclassification costs.
        % The Cost property is a square matrix with misclassification costs.
        % Cost(I,J) is the cost of misclassifying class ClassNames(I) as class
        % ClassNames(J).
        Cost
        %CATEGORICALPREDICTORS Indices of categorical predictors.
        % The CategoricalPredictors property is an array with indices of
        % categorical predictors. The indices are in the range from 1 to the
        % number of columns in X.
        CategoricalPredictors
        % CATEGORICALLEVELS Multivariate multinomial levels.
        % The CategoricalLevels property is a cell array of length P. If
        % DistributionNames{d} is 'mvmn', then CategoricalLevels{d} is a
        % numeric vector containing the unique levels of predictor d
        % found in the training data; otherwise CategoricalLevels{d} is
        % empty.
        CategoricalLevels
    end

    properties (Constant,Access = protected)
        IsBinary = false
        IsAdaptive = false
        LabelPredictor = @incremental.classif.ClassificationModel.minCost
        HasCost = true
        CanSupportCategorical = true
        HasAdditionalPredictionInputs = true
    end

    properties (Access = private)
        pModelSpecificMetrics = 'mincost'
    end

    properties(Constant, Access = protected)
        DefaultLoss = 'mincost'
    end

    properties(SetAccess = protected)
        DistributionNames
    end

    properties(Dependent,SetAccess = protected)
        DistributionParameters
    end

    properties (SetAccess = protected,Hidden)
        Impl = [];
    end

    properties (Access = protected)
        pCategoricalLevels
    end
    

    methods (Access = protected)
        function [modelArgs,remainingArgs] = parseMakeParameters(this,params) %#ok<INUSL>
            [modelArgs,remainingArgs] = incremental.impl.NaiveBayesImpl.parseMakeParameters(params);
        end

        function this = makeModel(this,parametersAreFromOffline,implParams)
            this.Impl = incremental.impl.NaiveBayesImpl.make(parametersAreFromOffline,implParams);
        end

        function [trainingArgs,remainingArgs] = parseTrainingParameters(this,params)
            [trainingArgs,remainingArgs] = parseTrainingParameters(this.Impl,params);
        end

        function this = fillTrainingParameters(this,parametersAreFromOffline,trainingParams)
            this.Impl = fillTrainingParameters(this.Impl,parametersAreFromOffline,trainingParams);
        end

        function this = fillDataParameters(this,parametersAreFromOffline,classParams)
            this = fillDataParameters@incremental.classif.ClassificationModel(this,parametersAreFromOffline,classParams);
        end

        function [classArgs,remainingArgs] = parseDataParameters(this,params)
            [classArgs,remainingArgs] = parseDataParameters@incremental.classif.ClassificationModel(this,params);
        end

        function this = fillStateParameters(this,parametersAreFromOffline,numPredictors,stateParams)
            this.Impl = fillStateParameters(this.Impl,parametersAreFromOffline,this.MaxNumClasses,numPredictors,stateParams,this.Prior,this.NumClasses,this.CategoricalPredictors,this.CategoricalLevels,this.WeightsPerClass);
        end

        function [stateArgs,remainingArgs] = parseStateParameters(this,params)
            [stateArgs,remainingArgs] = parseStateParameters(this.Impl,params);
        end

        function this = updateStateParameters(this,numPredictors,predictorClass)
            this.Impl = updateStateParameters(this.Impl,this.MaxNumClasses,numPredictors,predictorClass,this.Prior,this.NumClasses,this.pCategoricalPredictors,this.pCategoricalLevels);
        end

        function [categoricalDataArgs,remainingArgs] = parseCategoricalDataParameters(this,params)
            [categoricalDataArgs,remainingArgs] = parseCategoricalDataParameters@incremental.SimpleLearner(this,params);
            p = inputParser;
            p.StructExpand = true;
            p.KeepUnmatched = true;
            defaultcl = [];
            addParameter(p,'CategoricalLevels',defaultcl);
            parse(p,remainingArgs);
            categoricalDataArgs.CategoricalLevels = p.Results.CategoricalLevels;
            remainingArgs = p.Unmatched;
        end 

        function this = extractFromPredictorIfNeeded(this,X,obsInRows)
            this = extractFromPredictorIfNeeded@incremental.SimpleLearner(this,X,obsInRows);
            if isempty(this.pCategoricalLevels)
                this.pCategoricalLevels = cell(1,this.NumPredictors);
            end
        end        

        function this = fillCategoricalPredictors(this,parametersAreFromOffline,dataArgs)
            this = fillCategoricalPredictors@incremental.SimpleLearner(this,parametersAreFromOffline,dataArgs);
            if parametersAreFromOffline
                this.pCategoricalLevels = dataArgs.CategoricalLevels;
            elseif ~isempty(dataArgs.CategoricalLevels)
                error(message('stats:incremental:Learner:UnrecognizedNV','CategoricalLevels'));
            end
        end

        function categoricalPredictors = extractCategoricalPredictors(this,categoricalPredictors)
            % NB error check
            if ~isempty(this.NumPredictors) && this.NumPredictors~=0
                categoricalPredictors =  classreg.learning.FullClassificationRegressionModel.processCategoricalPredictors(...
                    categoricalPredictors,this.NumPredictors,1:this.NumPredictors);
            end
            errorCheckCategoricalDistNames(this.Impl,categoricalPredictors,this.NumPredictors,this.DistributionNames);
        end

        function this = updateCategoricalParameters(this,X)
            [this.Impl, catPreds, catBatch] = updateCategoricalParameters(this.Impl,X);
            this.pCategoricalPredictors = catPreds;
            for i=1:this.NumPredictors
                cats = this.pCategoricalLevels(i);
                catNew = catBatch(i);
                cats = cats{:};
                catNew = catNew{:};
                cat = union(cats,catNew,'stable');
                this.pCategoricalLevels(i) = {cat};
            end
        end

        function [this,X,Y,W,WisRaw,C] = prepareDataAndUpdateState(this,Xraw,Yraw,Wraw,obsInRows,numObservations)
            % update Prior, ClassNames and Weights if needed
            [this,X,Y,W,WisRaw,C] = prepareDataAndUpdateState@incremental.classif.ClassificationModel(this,Xraw,Yraw,Wraw,obsInRows,numObservations);
            this = updateStateParameters(this,this.NumPredictors,this.PredictorClass);
            this = updateCategoricalParameters(this,X);
        end

        function [numPredictors,predictorClass] = predictorAttributes(this)
            [numPredictors,predictorClass] = predictorAttributes(this.Impl);
        end

        function this = fitModelWithoutValidation(this,X,Y,W,WisRaw,obsInRows,numObservations)
            this.Impl = fit(this.Impl,X,Y,W,WisRaw,obsInRows,numObservations);
        end
    end

    methods (Access = protected)
        function this = updatePredictorInformation(this)
            this = updatePredictorInformation@incremental.SimpleLearner(this);
            if ~isempty(this.NumPredictors) && this.NumPredictors ~= 0
                this = updateStateParameters(this,this.NumPredictors,this.PredictorClass);
            end
        end

        function [predArgs,remainingArgs] = parsePredictionArgs(this,varargin)
            narginchk(1,7);
            [predArgs,remainingArgs] = parsePredictionArgs@incremental.classif.ClassificationModel(this,varargin{:});
            remainingArgs = incremental.utils.convertStructToCell(remainingArgs);
            p = inputParser;
            p.StructExpand = true;
            p.KeepUnmatched = true;
            addParameter(p,'Cost',[]);
            addParameter(p,'Prior',[]);
            parse(p,remainingArgs{:});
            predArgs.Cost = p.Results.Cost;
            predArgs.Prior = p.Results.Prior;
            remainingArgs = p.Unmatched;

            % No parameters should remain
            f = fieldnames(remainingArgs);
            if ~isempty(f)
                error(message('stats:incremental:Learner:UnrecognizedNV',f{1}));
            end
        end
    end

    methods
        function this = incrementalClassificationNaiveBayes(varargin)
            %INCREMENTALCLASSIFICATIONNAIVEBAYES Construct a naive Bayes classification model for incremental learning.
            %
            %   MODEL=INCREMENTALCLASSIFICATIONNAIVEBAYES('MAXNUMCLASSES',MaxNumClasses)
            %   returns a naive Bayes classification model for incremental learning.
            %   MaxNumClasses is the maximum number of classes expected in the response
            %   data during incremental learning. Default values of other parameters can be placeholders.
            %
            %   MODEL=INCREMENTALCLASSIFICATIONNAIVEBAYES('CLASSNAMES',ClassNames) specifies
            %   all class names ClassNames expected in the response data during incremental learning.
            %   Default values of other parameters can be placeholders.
            %
            %   MODEL=incrementalLearner(TTMODEL) returns a naive Bayes classification
            %   model for incremental learning initialized using the
            %   parameters provided in TTMODEL, a traditionally trained model.
            %   For details on how incrementalLearner is used for initialization of an incremental
            %   classification model, see the incrementalLearner reference for 
            %   ClassificationNaiveBayes.
            %
            %   MODEL=INCREMENTALCLASSIFICATIONNAIVEBAYES('PARAM1',val1,'PARAM2',val2,...) specifies optional
            %   parameter name/value arguments:
            %       'CategoricalPredictors'   - List of categorical predictors. Pass
            %                                   'CategoricalPredictors' as one of:
            %                                     * A numeric vector with indices between 1 and P,
            %                                       where P is the number of predictor variables.
            %                                     * A logical vector of length P, where a true
            %                                       entry means that the corresponding predictor
            %                                       is a categorical variable.
            %                                     * 'all', meaning all predictors are categorical.
            %                                   Default: None.
            %       'ClassNames'              - Array of class names, specified as a categorical array, logical
            %                                   vector, numeric vector, string array or
            %                                   cell array of strings.
            %                                   If MODEL is initialized from TTMODEL,
            %                                   TTMODEL.ClassNames is used.
            %                                   Default: 'ClassNames' is inferred from data when fit is called.
            %       'Cost'                    - Square matrix, where COST(I,J) is the
            %                                   cost of classifying a point into class J if its
            %                                   true class is I. Alternatively, COST can be a
            %                                   structure S with two fields: S.ClassificationCosts
            %                                   containing the cost matrix C, and S.ClassNames
            %                                   containing the class names and defining the
            %                                   ordering of classes used for the rows and columns
            %                                   of the cost matrix. For S.ClassNames use the data
            %                                   type that exists in Y.
            %                                   Default: COST(I,J)=1 if I~=J, and COST(I,J)=0 if I=J.
            %       'DistributionNames'       - A string or a 1-by-P cell vector of strings,
            %                                   specifying which distributions INCREMENTALCLASSIFICATIONNAIVEBAYES
            %                                   uses to model the data. If the value is a cell vector,
            %                                   its Jth element specifies the distribution
            %                                   INCREMENTALCLASSIFICATIONNAIVEBAYES uses for the Jth
            %                                   feature. The available types of distributions are:
            %                                   'normal'  Normal (Gaussian) distribution.
            %                                   'mvmn'    Multivariate multinomial distribution
            %                                       for discrete data. Each
            %                                       feature follows a multinomial
            %                                       model within a class. The parameters for a
            %                                       feature include the probabilities of all
            %                                       possible values that the corresponding
            %                                       feature can take.
            %                                    'mn'     Multinomial distribution for
            %                                       classifying count-based data such as in the
            %                                       bag-of-tokens model. In the bag-of-tokens
            %                                       model, the value of feature J is the
            %                                       number of occurrences of token J in
            %                                       this observation, so it must be a
            %                                       non negative integer.
            %                                       Also, each observation represents multiple
            %                                       trials of a multinomial distribution, and
            %                                       each occurrence of a token is one trial.
            %                                       The number of categories (bins) in this
            %                                       multinomial model is the number of distinct
            %                                       tokens, which is the number of columns of X.
            %                                   Default: 'mvmn' for categorical variables, and
            %                                    'normal' otherwise.
            %       'MaxNumClasses'           - Positive scalar specifying the maximum number of
            %                                   classes that this incremental naive bayes classifier
            %                                   will be trained on.
            %                                   Default: 'MaxNumClasses' is a required argument
            %                                   unless 'ClassNames' is specified, in which case,
            %                                   'MaxNumClasses' is the number of classes in
            %                                   'ClassNames'. If MODEL is trained from TTMODEL,
            %                                   'MaxNumClasses' is the number of classes
            %                                   TTMODEL was trained on.
            %       'Metrics'                 - String vector representing built-in
            %                                   metrics, cell array of function handles
            %                                   or a struct of function handles specifying
            %                                   metrics that are tracked during incremental
            %                                   training. 'Metrics' can be a
            %                                   combination of strings, function
            %                                   handles or struct of function handles
            %                                   passed as a cell array.
            %                                   Built-in metrics for classification:
            %                                   'mincost', 'binodeviance', 'classiferror',
            %                                   'exponential', 'hinge', 'logit', and 'quadratic'.
            %                                   If you pass a function handle FUN, it
            %                                   is called as shown below:
            %                                       FUN(C,S,COST)
            %                                   where C is an N-by-K logical matrix for N
            %                                   elements in Y and K classes in the ClassNames
            %                                   property, S is an N-by-K numeric matrix.
            %                                   C has one true per row for the true class.
            %                                   S is a matrix of predicted scores for classes
            %                                   with one row per observation, similar to score
            %                                   output from predict. COST is a
            %                                   matrix of misclassification costs.
            %                                   Metric name is extracted from provided function
            %                                   handle if it's not an anonymous
            %                                   function handle. If it is an anonymous
            %                                   function handle, metric is named
            %                                   as 'CustomMetric' and it is postfixed
            %                                   with an underscore and an integer
            %                                   identifying its relative position in
            %                                   'Metrics' property.
            %                                   If you pass a struct, fieldnames are
            %                                   treated as the metric names and
            %                                   values must be function handles.
            %                                   Incremental models track two values for
            %                                   each metric; a 'Cumulative' value and a
            %                                   'Window' value. 'MetricsWindowSize' determines
            %                                   the update frequency of 'Window' metrics.
            %                                   Default: 'mincost'.
            %       'MetricsWarmupPeriod'     - Nonnegative integer specifying the number of
            %                                   observations that are used for
            %                                   training the model prior to metrics
            %                                   update. If MODEL is initialized
            %                                   from TTMODEL, default is 0; otherwise, it is 1000.
            %       'MetricsWindowSize'       - Positive integer specifying the window
            %                                   size that is used for metrics
            %                                   update. Default value is 200.
            %       'NumPredictors'           - Non-negative scalar specifying the number of
            %                                   predictors.  If MODEL is initialized
            %                                   from TTMODEL, number of predictors from TTMODEL is used.
            %                                   Default: 'NumPredictors' is inferred from data when fit is called.
            %       'Prior'                   - Prior probabilities for each class. Specify as one
            %                                   of:
            %                                      * A string:
            %                                          - 'empirical' determines class probabilities
            %                                            from class frequencies in Y
            %                                          - 'uniform' sets all class probabilities equal
            %                                      * A vector (one scalar value for each class)
            %                                        If you pass numeric values, they are normalized to
            %                                        add up to 1.
            %                                   If MODEL is initialized from TTMODEL,
            %                                   TTMODEL.Prior is used.
            %                                   Default: 'empirical'.
            %                                   If 'ClassNames' is not provided, Prior must be 'empirical'.
            %       'ScoreTransform'          - Function handle for transforming scores, or
            %                                   string representing a built-in transformation
            %                                   function. Available functions: 'symmetric',
            %                                   'invlogit', 'ismax', 'symmetricismax', 'none',
            %                                   'logit', 'doublelogit', 'symmetriclogit', and
            %                                   'sign'.
            %                                   If MODEL is initialized from TTMODEL,
            %                                   TTMODEL.ScoreTransform is used.
            %                                   Default: 'none'.
            %
            %   See also incrementalLearner.
            this = this@incremental.classif.ClassificationModel(varargin{:});
            this.IgnoreXNaNs = true;         
            this.WeightsNeededForPredictorClass = true;
            this.WeightClassFixed = false;
        end

        function [varargout] = predict(this,X,varargin)
            %PREDICT Predict responses of the model.
            %   [LABEL,SCORE,COST]=PREDICT(MODEL,X) returns predicted class
            %   labels, posterior probabilities and misclassification costs for model MODEL and
            %   predictors X. Pass X as a matrix with
            %   N observations from P predictors.
            %   Classification labels LABEL are an N-by-1 array of the same type
            %   as Y used for training, where N is the number of observations (rows in
            %   X). Scores SCORE are an N-by-K numeric matrix for N observations
            %   and K classes. COST is an N-by-K matrix with predicted misclassification
            %   costs per class. The predicted label is assigned to the class with the minimal
            %   misclassification cost.
            %
            %   [LABEL,SCORE,COST]=PREDICT(MODEL,X,'PARAM1',val1,'PARAM2',val2,...) specifies
            %   optional parameter name/value pairs:
            %       'Cost'                - Square matrix, where COST(I,J) is the
            %                               cost of classifying a point into class J if its
            %                               true class is I. Alternatively, COST can be a
            %                               structure S with two fields: S.ClassificationCosts
            %                               containing the cost matrix C, and S.ClassNames
            %                               containing the class names and defining the
            %                               ordering of classes used for the rows and columns
            %                               of the cost matrix. For S.ClassNames use the data
            %                               type that exists in Y. 
            %                               Default: MODEL.Cost.
            %       'Prior'               - Vector of prior probabilities for each class.
            %                               If you specify a numeric vector, probabilities are normalized to
            %                               sum to 1.
            %                               Default: MODEL.Prior.
            %       'ScoreTransform'      - Function handle for transforming scores, or
            %                               string representing a built-in transformation
            %                               function. Available functions include: 'symmetric',
            %                               'invlogit', 'ismax', 'symmetricismax', 'none',
            %                               'logit', 'doublelogit', 'symmetriclogit', and
            %                               'sign'.
            %                               Default: MODEL.ScoreTransform.
            %
            %   See also incrementalClassificationNaiveBayes, logp, fit.


            % Check for ObservationsIn NVP and error out if present
            checkObservationsIn(this,varargin{:});
            [varargout{1:nargout}] = predict@incremental.classif.ClassificationModel(this,X,varargin{:});
        end

        function this = fit(this,X,Y,varargin)
            %FIT Train model with new data.
            %   MODEL=FIT(MODEL,X,Y) returns the modified MODEL
            %   trained incrementally with new data X and Y.
            %   MODEL.Metrics are not updated inside fit.
            %   Pass X as a matrix with N observations from P predictors.
            %   Pass Y as a categorical array, character array, logical
            %   vector, numeric vector, string
            %   array or cell array of strings. If Y is a character array,
            %   it must have one class label per row. Otherwise Y
            %   must be a vector with N elements.
            %   If MODEL.ClassNames is specified, Y must be of the same data type as
            %   MODEL.ClassNames. If MODEL.ClassNames is not defined,
            %   MODEL.ClassNames is inferred from data.
            %   Y must have 'MaxNumClasses' or less number of classes.
            %
            %   MODEL=FIT(MODEL,X,Y,'PARAM1',val1,'PARAM2',val2,...) specifies
            %   optional parameter name/value pairs:
            %       'Weights'          - Vector of observation weights. By default, the
            %                            weight of every observation is set to 1. The
            %                            length of this vector must be equal to the
            %                            number of observations in X.
            %
            %   See also incrementalClassificationNaiveBayes, updateMetricsAndFit, updateMetrics, loss.

            % Check for ObservationsIn NVP and error out if present
            checkObservationsIn(this,varargin{:});
            this = fit@incremental.SimpleLearner(this,X,Y,varargin{:});
        end

        function l = loss(this,X,Y,varargin)
            %LOSS Model error.
            %   ERR=LOSS(MODEL,X,Y) returns error for model MODEL computed
            %   using predictors X and true observed values Y.
            %   Pass X as a matrix with N observations from P predictors.
            %   Y must be of the same data type as
            %   MODEL.ClassNames and have N elements. ERR is a scalar.
            %
            %   ERR=LOSS(MODEL,X,Y,'PARAM1',val1,'PARAM2',val2,...) specifies optional
            %   parameter name/value pairs:
            %       'Cost'               - Square matrix, where COST(I,J) is the
            %                              cost of classifying a point into class J if its
            %                              true class is I. Alternatively, COST can be a
            %                              structure S with two fields: S.ClassificationCosts
            %                              containing the cost matrix C, and S.ClassNames
            %                              containing the class names and defining the
            %                              ordering of classes used for the rows and columns
            %                              of the cost matrix. For S.ClassNames use the data
            %                              type that exists in Y.
            %                              Default: MODEL.Cost.
            %       'LossFun'            - Function handle for loss, or string
            %                              representing a built-in loss function.
            %                              Available loss functions for classification:
            %                              'mincost', 'binodeviance', 'classiferror', 'exponential',
            %                              'hinge', 'logit', and 'quadratic'.
            %                              If you pass a function handle FUN, it
            %                              is called as shown below:
            %                                    FUN(C,S,W,COST)
            %                              where C is an N-by-K logical matrix for N
            %                              elements in Y and K classes in the ClassNames
            %                              property, S is an N-by-K numeric matrix and W is
            %                              a numeric vector with N elements. C has one true per
            %                              row for the true class. S is a matrix of
            %                              predicted scores 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'.
            %       'Prior'              - Vector of prior probabilities for each class.
            %                              If you specify a numeric vector, probabilities are normalized to
            %                              sum to 1.
            %                              Default: MODEL.Prior.
            %       'ScoreTransform'     - Function handle for transforming scores, or
            %                              string representing a built-in transformation
            %                              function. Available functions include: 'symmetric',
            %                              'invlogit', 'ismax', 'symmetricismax', 'none',
            %                              'logit', 'doublelogit', 'symmetriclogit', and
            %                              'sign'.
            %                              Default: MODEL.ScoreTransform.
            %       'Weights'            - Vector of observation weights. By default, the
            %                              weight of every observation is 1. The
            %                              length of this vector must be equal to the
            %                              number of observations in X.
            %
            %   See also incrementalClassificationNaiveBayes, updateMetrics, updateMetricsAndFit, fit,
            %   predict, perObservationLoss.

            % Check for ObservationsIn NVP and error out if present
            checkObservationsIn(this,varargin{:});
            l = loss@incremental.SimpleLearner(this,X,Y,varargin{:});
        end

        function l = perObservationLoss(this,X,Y,varargin)
            %PEROBSERVATIONLOSS Model per observation classification error.
            %   ERR=PEROBSERVATIONLOSS(MODEL,X,Y) returns per observation
            %   classification error for model MODEL computed using predictors X and
            %   true labels Y. Pass X as an N-by-P matrix for N observations and P
            %   predictors. Y must be of the same data type as MODEL.ClassNames and have N
            %   elements. ERR is a N-by-1 vector.
            %
            %   ERR=PEROBSERVATIONLOSS(MODEL,X,Y,'PARAM1',val1,'PARAM2',val2,...) specifies optional
            %   parameter name/value pairs:
            %       'LossFun'          - Function handle for loss, or string
            %                            representing a built-in loss function.
            %                            Available loss functions for classification:
            %                            'mincost','binodeviance', 'classiferror', 'exponential',
            %                            'hinge', 'logit', and 'quadratic'.
            %                            If you pass a function handle FUN, it
            %                            is called as shown below:
            %                                  FUN(C,S)
            %                            where C is an N-by-K logical matrix for N
            %                            elements in Y and K classes in the ClassNames
            %                            property and S is an N-by-K numeric matrix. 
            %                            C has one true per row for the true class. 
            %                            S is a matrix of predicted scores for classes
            %                            with one row per observation, similar to SCORE 
            %                            output from PREDICT.
            %                            Default: 'mincost'.
            %
            %   See also incrementalClassificationNaiveBayes, updateMetrics, updateMetricsAndFit, fit,
            %   predict, loss.
            
            % Check for ObservationsIn NVP and error out if present
            checkObservationsIn(this,varargin{:});            
            l = perObservationLoss@incremental.SimpleLearner(this,X,Y,varargin{:});
        end        

        function lp = logp(this,X)
            %LOGP Log of the unconditional probability density.
            %   LOGP = LOGP(MODEL,X) returns a column vector with N elements for N rows in
            %   the predictors X. X is a matrix with P columns, where P
            %   is the number of predictors used for training NB. Element I in this vector
            %   is the log of the probability of observing row I of X computed using
            %   MODEL. LOGP is NaN for rows of X containing any
            %   NaNs.
            %
            %   See also incrementalClassificationNaiveBayes, predict, fit.
            lp = logp(this.Impl,X);
        end

        function  this = updateMetrics(this,X,Y,varargin)
            %UPDATEMETRICS Update metrics with new data.
            %   MODEL=UPDATEMETRICS(MODEL,X,Y) returns the modified MODEL
            %   for which the metrics, specified by 'Metrics' Name-Value pair
            %   during object construction, are updated with new data
            %   X and Y.
            %   Pass X as a matrix with N observations from P predictors.
            %   For classification models, Y must be of the same data type as
            %   MODEL.ClassNames and have N elements.
            %
            %   MODEL=UPDATEMETRICS(MODEL,X,Y,'PARAM1',val1,'PARAM2',val2,...) specifies
            %   optional parameter name/value pairs:
            %       'Weights'          - Vector of observation weights. By default, the
            %                            weight of every observation is set to 1. The
            %                            length of this vector must be equal to the
            %                            number of observations in X.
            %
            %   See also incrementalClassificationNaiveBayes, updateMetricsAndFit, fit, loss, predict.

            % Check for ObservationsIn NVP and error out if present
            checkObservationsIn(this,varargin{:});
            this = updateMetrics@incremental.SimpleLearner(this,X,Y,varargin{:});
        end

        function  this = updateMetricsAndFit(this,X,Y,varargin)
            %UPDATEMETRICSANDFIT Update metrics and train model with new data.
            %   MODEL=UPDATEMETRICSANDFIT(MODEL,X,Y) returns the MODEL
            %   with the following modifications:
            %           1. Metrics, specified by 'Metrics' Name-Value pair
            %              during object construction, are updated.
            %           2. Model trained incrementally with new X and Y.
            %
            %   Pass X as a matrix with N observations from P predictors.
            %   For classification models, Y must be of the same data type as
            %   MODEL.ClassNames and have N elements.
            %
            %   MODEL=UPDATEMETRICSANDFIT(MODEL,X,Y,'PARAM1',val1,'PARAM2',val2,...) specifies
            %   optional parameter name/value pairs:
            %       'Weights'          - Vector of observation weights. By default, the
            %                            weight of every observation is set to 1. The
            %                            length of this vector must be equal to the
            %                            number of observations in X.
            %
            %   See also incrementalClassificationNaiveBayes, updateMetrics, fit, loss, predict.

            % Check for ObservationsIn NVP and error out if present
            checkObservationsIn(this,varargin{:});
            this = updateMetricsAndFit@incremental.SimpleLearner(this,X,Y,varargin{:});
        end     
    end

    methods
        function cost = get.Cost(this)
            temp = this.pCost(1:this.NumClasses,1:this.NumClasses);
            cost = 1-eye(this.NumClasses);
            cost(logical(cost)) = NaN;
            inds = find(this.Prior);
            cost(:,inds) = temp(:,inds);
        end

        function cp = get.CategoricalPredictors(this)
            cp = this.pCategoricalPredictors;
        end

        function cl = get.CategoricalLevels(this)
            cl = this.pCategoricalLevels;
        end

        function distnames = get.DistributionNames(this)
            distnames = this.Impl.DistributionNames;
        end

        function distparams = get.DistributionParameters(this)
            distparams = this.Impl.DistributionParameters;
        end
    end

    methods(Access = protected)
        function checkObservationsIn(~,varargin)

            % Extract string valued inputs and check if any is contained in
            % ObservationsIn. If true, error
            stringinputs = cellfun(@(x)isstring(x) || ischar(x),varargin);
            if any(cellfun(@(x)contains('ObservationsIn',x,'IgnoreCase',true),varargin(stringinputs)))
                error(message('stats:incremental:Learner:UnrecognizedNV','ObservationsIn'));
            end
        end

        function metrics = allowedMetrics(this)
            % allowed metrics for incremental classification models
            metrics = allowedMetrics@incremental.classif.ClassificationModel(this);
            metrics = [this.pModelSpecificMetrics metrics];
        end

        function losses = allowedPerObservationLosses(this)
            % allowed per observation losses for incremental classification
            % Naive Bayes models
            losses = allowedPerObservationLosses@incremental.classif.ClassificationModel(this);
            losses = [this.pModelSpecificMetrics losses];
        end        

        function [this,remainingArgs] = configureForEstimation(this,parametersAreFromOffline,numPredictors,predictorClass,args) %#ok<INUSL>
            remainingArgs = args;
        end

        % There are no parameters in Naive Bayes to estimate. Since
        % allocateEstimationBuffers is abstract in the super class Learner,
        % it needs to be implemented here. Thus the function body is blank.
        function this = allocateEstimationBuffers(this,~,~,~,~,~,~,~)
        end

        % There are no parameters in Naive Bayes to estimate. Since
        % prepareDataAndUpdateEstimationState is abstract in the super class
        % Learner, it needs to be implemented here. Thus the function body
        % is blank.
        function [this,X,Y,W] = prepareDataAndUpdateEstimationState(this,X,Y,W,~,~)
        end

        function this = updateEstimatedParameters(this,X,Y,W,obsInRows,numEstimationObservations) %#ok<INUSD>
        end

        function f = score(this,X,obsInRows,prior,cost)
            % score for incremental classification model
            f = score(this.Impl,X,obsInRows,prior,cost);
        end

        function cancalculateloss = canCalculateLoss(this)
            % can model calculate loss
            cancalculateloss = canCalculateLoss@incremental.classif.ClassificationModel(this);
        end

        function cantrain = canTrain(this)
            % can model train
            % This model can always be trained since we are not estimating
            % any parameters, thus no estimation period
            cantrain = canTrain@incremental.SimpleLearner(this);
        end

        function statesdefined = statesDefined(this)
            % just a method that says the model's states are defined and is
            % ready to predict
            statesdefined = size(this.DistributionParameters,1)==this.NumClasses ...
                && size(this.DistributionParameters,2)==this.NumPredictors;
        end

        function this = resetModel(this,varargin)
            % Reset states 
            this = resetModel@incremental.classif.ClassificationModel(this,varargin{:});
            this.Impl = reset(this.Impl,this.Prior);
            this = resetCategoricalLevels(this);
        end

        function propList = propertiesForDisplay(this,propList)
            propList = propertiesForDisplay@incremental.classif.ClassificationModel(this,propList);
            propList.DistributionNames = this.DistributionNames;
            propList.DistributionParameters = this.DistributionParameters;
        end

        function this = resetCategoricalLevels(this)
            for i=1:this.NumPredictors
                cats = zeros(0,1,'like',this.pCategoricalLevels{i});
                this.pCategoricalLevels(i) = {cats};
            end
        end

    end

    methods (Hidden)
        function str = serialize(this)
            % Convert incremental model to deserializeable struct
            str = serializeHelper(this);
        end
    end
    methods (Static,Hidden)
        function this = deserialize(deserializeableStr)
            % construct incremental model from a given struct
            this = incrementalClassificationNaiveBayes('MaxNumClasses',1);           
            this = deserialize@incremental.classif.ClassificationModel(this,deserializeableStr);
            if ~isfield(deserializeableStr,'CategoricalPredictors')  %if CategoricalPredictors is not a field, struct is saved prior to R2021b
                this.pCategoricalLevels = [];
            else
                this.pCategoricalLevels = deserializeableStr.pCategoricalLevels;
            end
            this.Impl = deserialize(this.Impl,deserializeableStr.ModelParameters);
        end
    end

    methods (Static,Hidden)
        function this = loadobj(obj)
            % custom load method for backwards compatibility
            if isstruct(obj)
                deserializeableStr = incrementalClassificationNaiveBayes.serializeStruct(obj);
                this = incrementalClassificationNaiveBayes.deserialize(deserializeableStr);
            else
                this = obj;
            end
            if isempty(this.WeightsPerClass)
                this = setWeightsPerClass(this,this.Impl.NumTotalObservations);
                this.Impl.WeightsPerClass = this.WeightsPerClass;
            end
            if isempty(this.pCategoricalPredictors)
                this.pCategoricalPredictors = [];
                this.Impl.pCategoricalPredictors = this.pCategoricalPredictors;
            end
            if isempty(this.pCategoricalLevels)
                this.pCategoricalLevels = repmat({zeros(0,1)},1,this.NumPredictors);
                this.Impl.pCategoricalLevels = this.pCategoricalLevels;
            end
        end

        function str = serializeStruct(this)
            % Convert incremental model struct to deserializeable
            % struct.
            str = serializeHelper(this);
        end
    end
end

function str = serializeHelper(this)
    inputIsStruct = isstruct(this);
    str = incremental.SimpleLearner.serialize(this);
    if inputIsStruct
        if isfield(this,'CanSupportCategorical')
            str.pCategoricalLevels = this.pCategoricalLevels;
        end
    else
        if this.CanSupportCategorical
            str.pCategoricalLevels = this.pCategoricalLevels;
        end
    end
    str = incremental.classif.ClassificationModel.serialize(this,str);
    str.ModelParameters = serialize(this.Impl);
end
