## Copyright (C) 2024-2025 Andreas Bertsatos <abertsatos@biol.uoa.gr>
##
## This file is part of the statistics package for GNU Octave.
##
## This program is free software; you can redistribute it and/or modify it under
## the terms of the GNU General Public License as published by the Free Software
## Foundation; either version 3 of the License, or (at your option) any later
## version.
##
## This program is distributed in the hope that it will be useful, but WITHOUT
## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
## details.
##
## You should have received a copy of the GNU General Public License along with
## this program; if not, see <http://www.gnu.org/licenses/>.

## -*- texinfo -*-
## @deftypefn  {statistics} {@var{pd} =} fitdist (@var{x}, @var{distname})
## @deftypefnx {statistics} {@var{pd} =} fitdist (@var{x}, @var{distname}, @var{Name}, @var{Value})
## @deftypefnx {statistics} {[@var{pdca}, @var{gn}, @var{gl}] =} fitdist (@var{x}, @var{distname}, @qcode{'By'}, @var{groupvar})
## @deftypefnx {statistics} {[@var{pdca}, @var{gn}, @var{gl}] =} fitdist (@var{x}, @var{distname}, @qcode{'By'}, @var{groupvar}, @var{Name}, @var{Value})
##
## Create probability distribution object.
##
## @code{@var{pd} = fitdist (@var{x}, @var{distname})} creates a probability
## distribution object by fitting the distribution specified by
## @var{distname} to the data in vector @var{x}.
##
## @code{@var{pd} = fitdist (@var{x}, @var{distname}, @var{Name}, @var{Value})}
## creates the probability distribution object with additional options specified
## by one or more @qcode{Name-Value} pair arguments listed below.
##
## @multitable @columnfractions 0.18 0.8
## @headitem @var{Name} @tab @var{Value}
##
## @item @qcode{'distribution'} @tab A character vector specifying the
## distribution type for which to estimate parameters.
##
## @item @qcode{'Ntrials'} @tab A scalar specifying the number of trials
## for the corresponding element of @var{x} for the binomial distribution.
##
## @item @qcode{'theta'} @tab A scalar specifying the location parameter
## for the generalized Pareto distribution.  It defaults to 0, as MATLAB
## assumes it, and is not estimated.
##
## @item @qcode{'mu'} @tab A scalar specifying the location parameter
## for the half-normal distribution.
##
## @item @qcode{'censoring'} @tab A vector of the same size as @var{x}
## indicating censored data in @var{x}.  By default it is
## @qcode{@var{censor} = zeros (size (@var{x}))}.
##
## @item @qcode{'frequency'} @tab A vector of nonnegative integer counts of
## the same size as @var{x} used as frequency observations.  By default it is
## @qcode{@var{freq} = ones (size (@var{x}))}.
##
## @item @qcode{'alpha'} @tab A scalar in the range @math{(0,1)}, as the
## significance level for the confidence interval @var{pci}.  By default it is
## 0.05 corresponding to 95% confidence intervals.
##
## @item @qcode{'options'} @tab A structure specifying the control
## parameters for the iterative algorithm used to compute ML estimates with the
## @code{fminsearch} function.
## @end multitable
##
## @code{[@var{pdca}, @var{gn}, @var{gl}] = fitdist (@var{x}, @var{distname},
## @qcode{'By'}, @var{groupvar})} creates probability distribution objects by
## fitting the distribution specified by @var{distname} to the data in @var{x}
## based on the grouping variable @var{groupvar}. It returns a cell array of
## fitted probability distribution object, @var{pdca}, a cell array of group
## labels, @var{gn}, and a cell array of grouping variable levels, @var{gl}.
##
## @code{[@var{pdca}, @var{gn}, @var{gl}] = fitdist (@var{x}, @var{distname},
## @qcode{'By'}, @var{groupvar}, @var{Name}, @var{Value})} returns the same
## output arguments using additional options specified by one or more
## @qcode{Name-Value} pair arguments mentioned above.
##
## Note: calling @code{fitdist} without any input arguments will return a cell
## array of character vectors listing all supported distributions.
##
## Distribution names are matched ignoring case, spaces and hyphens, so that
## @qcode{'Extreme Value'}, @qcode{'ExtremeValue'} and @qcode{'extreme-value'}
## all select the same distribution, and the same set of names is accepted by
## @code{cdf}, @code{pdf}, @code{icdf}, @code{random}, @code{makedist},
## @code{fitdist} and @code{mle}.
##
## This accepts more names than MATLAB.  MATLAB takes the spaced and the
## squashed spelling but refuses the hyphenated one, so
## @qcode{'Birnbaum-Saunders'} and @qcode{'Log-Logistic'} are errors there;
## Octave has always accepted them and continues to.  MATLAB also accepts
## @qcode{'tLocationScale'} in @code{makedist} while refusing it in
## @code{cdf} for the same distribution; Octave accepts it, and
## @qcode{'location-scale T'}, everywhere.  Code written against MATLAB's
## names therefore runs unchanged, but code relying on these names will not
## port back.
##
## @seealso{makedist}
## @end deftypefn

function [varargout] = fitdist (varargin)

  ## Add list of supported probability distribution objects
  PDO = {'Beta'; 'Binomial'; 'BirnbaumSaunders'; 'Burr'; 'Exponential'; ...
         'ExtremeValue'; 'Gamma'; 'GeneralizedExtremeValue'; ...
         'GeneralizedPareto'; 'HalfNormal'; 'InverseGaussian'; ...
         'Kernel'; 'Logistic'; 'Loglogistic'; 'Lognormal'; 'Nakagami'; ...
         'NegativeBinomial'; 'Normal'; 'Poisson'; 'Rayleigh'; 'Rician'; ...
         'Stable'; 'tLocationScale'; 'Weibull'};

  ABBR = {'bisa', 'ev', 'gev', 'gp', 'hn', 'invg', 'nbin', 'tls'};

  ## Check for input arguments
  if (nargin == 0)
    varargout{1} = PDO;
    return
  elseif (nargin == 1)
    error ("fitdist: DISTNAME is required.");
  else
    x = varargin{1};
    distname = varargin{2};
    varargin([1:2]) = [];
  endif

  ## Check distribution name
  if (! (ischar (distname) && size (distname, 1) == 1))
    error ("fitdist: DISTNAME must be a character vector.");
  elseif (! (any (strcmpi (distname, PDO)) || any (strcmpi (distname, ABBR))))
    error ("fitdist: unrecognized distribution name.");
  endif

  ## Check data in X being a real vector
  if (! (isvector (x) && isnumeric (x) && isreal (x)))
    error ("fitdist: X must be a numeric vector of real values.");
  endif

  ## Add defaults
  groupvar = [];
  censor = zeros (size (x));
  freq = ones (size (x));
  alpha = 0.05;
  ntrials = 1;
  mu = 0;
  theta = 0;
  kernel = 'normal';
  ksupport = 'unbounded';
  kwidth = [];
  options.Display = 'off';
  options.MaxFunEvals = 400;
  options.MaxIter = 200;
  options.TolX = 1e-6;

  ## Parse extra arguments
  if (mod (numel (varargin), 2) != 0)
    error ("fitdist: optional arguments must be in NAME-VALUE pairs.");
  endif
  while (numel (varargin) > 0)
    switch (tolower (varargin{1}))
      case 'by'
        groupvar = varargin{2};
        if (! isequal (size (x), size (groupvar)) && ! isempty (groupvar))
          error (strcat ("fitdist: GROUPVAR argument must have the same", ...
                         " size as the input data in X."));
        endif
      case 'censoring'
        censor = varargin{2};
        if (! isequal (size (x), size (censor)))
          error (strcat ("fitdist: 'censoring' argument must have the", ...
                         " same size as the input data in X."));
        endif
      case 'frequency'
        freq = varargin{2};
        if (! isequal (size (x), size (freq)))
          error (strcat ("fitdist: 'frequency' argument must have the", ...
                         " same size as the input data in X."));
        endif
        if (any (freq != round (freq)) || any (freq < 0))
          error (strcat ("fitdist: 'frequency' argument must contain", ...
                         " non-negative integer values."));
        endif
      case 'alpha'
        alpha = varargin{2};
        if (! isscalar (alpha) || ! isreal (alpha) || alpha <= 0 || alpha >= 1)
          error ("fitdist: invalid value for 'alpha' argument.");
        endif
      case 'ntrials'
        ntrials = varargin{2};
        if (! (isscalar (ntrials) && isreal (ntrials) && ntrials > 0
                                  && fix (ntrials) == ntrials))
          error (strcat ("fitdist: 'ntrials' argument must be a positive", ...
                         " integer scalar value."));
        endif
      case {'mu'}
        mu = varargin{2};
      case {'theta'}
        theta = varargin{2};
      case 'options'
        options = varargin{2};
        if (! isstruct (options) || ! isfield (options, 'Display') ||
            ! isfield (options, 'MaxFunEvals') || ! isfield (options, 'MaxIter')
                                               || ! isfield (options, 'TolX'))
          error (strcat ("fitdist: 'options' argument must be a", ...
                         " structure compatible for 'fminsearch'."));
        endif

      case 'kernel'
        kernel = varargin{2};
      case 'support'
        ksupport = varargin{2};
      case 'width'
        kwidth = varargin{2};
      otherwise
        error ("fitdist: unknown parameter name.");
    endswitch
    varargin([1:2]) = [];
  endwhile

  ## Handle missing values
  is_nan = isnan (x) | isnan (censor) | isnan (freq);
  if (any (is_nan))
    x(is_nan) = [];
    censor(is_nan) = [];
    freq(is_nan) = [];
  endif
  if (isempty (x))
    error ("fitdist: no data in X to fit a '%s' distribution.", distname);
  endif

  ## Handle group variable
  if (isempty (groupvar) && nargout > 1)
    error ("fitdist: must define GROUPVAR for more than one output arguments.");
  endif
  if (! isempty (groupvar))
    [g, gn, gl] = grp2idx (groupvar);
    groups = numel (gn);
    if (any (is_nan))
      groupvar(is_nan) = [];
      g = grp2idx (groupvar);
    endif
  endif

  ## Warning message for no group data
  msg = 'fitdist: no data in group ''%s'' to fit a ''%s'' distribution.';

  ## Switch to selected distribution
  switch (__distname_key__ (distname))

    case 'beta'
      if (isempty (groupvar))
        varargout{1} = prob.BetaDistribution.fit (x, alpha, freq, options);
      else
        pd = cell (1, groups);
        for i = 1:groups
          x_i = x(g == i);
          f_i = freq(g == i);
          if (isempty (x_i))
            pd{i} = [];
            warning (msg, gn{i}, distname);
          else
            pd{i} = prob.BetaDistribution.fit (x_i, alpha, f_i, options);
          endif
        endfor
        varargout{1} = pd;
        varargout{2} = gn;
        varargout{3} = gl;
      endif

    case 'binomial'
      if (any (x > ntrials))
        error ("fitdist: invalid NTRIALS value for Binomial distribution.")
      endif
      if (isempty (groupvar))
        varargout{1} = prob.BinomialDistribution.fit (x, ntrials, alpha, freq);
      else
        pd = cell (1, groups);
        for i = 1:groups
          x_i = x(g == i);
          f_i = freq(g == i);
          if (isempty (x_i))
            pd{i} = [];
            warning (msg, gn{i}, distname);
          else
            pd{i} = prob.BinomialDistribution.fit (x_i, ntrials, alpha, f_i);
          endif
        endfor
        varargout{1} = pd;
        varargout{2} = gn;
        varargout{3} = gl;
      endif

    case {'birnbaumsaunders', 'bisa'}
      if (isempty (groupvar))
        varargout{1} = prob.BirnbaumSaundersDistribution.fit ...
                       (x, alpha, censor, freq, options);
      else
        pd = cell (1, groups);
        for i = 1:groups
          x_i = x(g == i);
          c_i = censor(g == i);
          f_i = freq(g == i);
          if (isempty (x_i))
            pd{i} = [];
            warning (msg, gn{i}, distname);
          else
            pd{i} = prob.BirnbaumSaundersDistribution.fit ...
                    (x_i, alpha, c_i, f_i, options);
          endif
        endfor
        varargout{1} = pd;
        varargout{2} = gn;
        varargout{3} = gl;
      endif

    case 'burr'
      if (isempty (groupvar))
        varargout{1} = prob.BurrDistribution.fit (x, alpha, censor, freq, options);
      else
        pd = cell (1, groups);
        for i = 1:groups
          x_i = x(g == i);
          c_i = censor(g == i);
          f_i = freq(g == i);
          if (isempty (x_i))
            pd{i} = [];
            warning (msg, gn{i}, distname);
          else
            pd{i} = prob.BurrDistribution.fit (x_i, alpha, c_i, f_i, options);
          endif
        endfor
        varargout{1} = pd;
        varargout{2} = gn;
        varargout{3} = gl;
      endif

    case 'exponential'
      if (isempty (groupvar))
        varargout{1} = prob.ExponentialDistribution.fit (x, alpha, censor, freq);
      else
        pd = cell (1, groups);
        for i = 1:groups
          x_i = x(g == i);
          c_i = censor(g == i);
          f_i = freq(g == i);
          if (isempty (x_i))
            pd{i} = [];
            warning (msg, gn{i}, distname);
          else
            pd{i} = prob.ExponentialDistribution.fit (x_i, alpha, c_i, f_i);
          endif
        endfor
        varargout{1} = pd;
        varargout{2} = gn;
        varargout{3} = gl;
      endif

    case {'extremevalue', 'ev'}
      if (isempty (groupvar))
        varargout{1} = prob.ExtremeValueDistribution.fit ...
                       (x, alpha, censor, freq, options);
      else
        pd = cell (1, groups);
        for i = 1:groups
          x_i = x(g == i);
          c_i = censor(g == i);
          f_i = freq(g == i);
          if (isempty (x_i))
            pd{i} = [];
            warning (msg, gn{i}, distname);
          else
            pd{i} = prob.ExtremeValueDistribution.fit (x_i, alpha, c_i, f_i, options);
          endif
        endfor
        varargout{1} = pd;
        varargout{2} = gn;
        varargout{3} = gl;
      endif

    case 'gamma'
      if (isempty (groupvar))
        varargout{1} = prob.GammaDistribution.fit (x, alpha, censor, freq, options);
      else
        pd = cell (1, groups);
        for i = 1:groups
          x_i = x(g == i);
          c_i = censor(g == i);
          f_i = freq(g == i);
          if (isempty (x_i))
            pd{i} = [];
            warning (msg, gn{i}, distname);
          else
            pd{i} = prob.GammaDistribution.fit (x_i, alpha, c_i, f_i, options);
          endif
        endfor
        varargout{1} = pd;
        varargout{2} = gn;
        varargout{3} = gl;
      endif

    case {'generalizedextremevalue', 'gev'}
      if (isempty (groupvar))
        varargout{1} = prob.GeneralizedExtremeValueDistribution.fit ...
                       (x, alpha, freq, options);
      else
        pd = cell (1, groups);
        for i = 1:groups
          x_i = x(g == i);
          f_i = freq(g == i);
          if (isempty (x_i))
            pd{i} = [];
            warning (msg, gn{i}, distname);
          else
            pd{i} = prob.GeneralizedExtremeValueDistribution.fit ...
                    (x_i, alpha, f_i, options);
          endif
        endfor
        varargout{1} = pd;
        varargout{2} = gn;
        varargout{3} = gl;
      endif

    case {'generalizedpareto', 'gp'}
      if (any (x - theta < 0))
        error (strcat ("fitdist: invalid THETA value for generalized", ...
                       " Pareto distribution."));
      endif
      if (isempty (groupvar))
        varargout{1} = prob.GeneralizedParetoDistribution.fit ...
                       (x, theta, alpha, freq, options);
      else
        pd = cell (1, groups);
        for i = 1:groups
          x_i = x(g == i);
          f_i = freq(g == i);
          if (isempty (x_i))
            pd{i} = [];
            warning (msg, gn{i}, distname);
          else
            pd{i} = prob.GeneralizedParetoDistribution.fit ...
                    (x_i, theta, alpha, f_i, options);
          endif
        endfor
        varargout{1} = pd;
        varargout{2} = gn;
        varargout{3} = gl;
      endif

    case {'halfnormal', 'hn'}
      if (any (x - mu < 0))
        error ("fitdist: invalid MU value for half-normal distribution.");
      endif
      if (isempty (groupvar))
        varargout{1} = prob.HalfNormalDistribution.fit (x, mu, alpha, freq);
      else
        pd = cell (1, groups);
        for i = 1:groups
          x_i = x(g == i);
          f_i = freq(g == i);
          if (isempty (x_i))
            pd{i} = [];
            warning (msg, gn{i}, distname);
          else
            pd{i} = prob.HalfNormalDistribution.fit (x_i, mu, alpha, f_i);
          endif
        endfor
        varargout{1} = pd;
        varargout{2} = gn;
        varargout{3} = gl;
      endif

    case {'inversegaussian', 'invg'}
      if (isempty (groupvar))
        varargout{1} = prob.InverseGaussianDistribution.fit ...
                       (x, alpha, censor, freq, options);
      else
        pd = cell (1, groups);
        for i = 1:groups
          x_i = x(g == i);
          c_i = censor(g == i);
          f_i = freq(g == i);
          if (isempty (x_i))
            pd{i} = [];
            warning (msg, gn{i}, distname);
          else
            pd{i} = prob.InverseGaussianDistribution.fit ...
                    (x_i, alpha, c_i, f_i, options);
          endif
        endfor
        varargout{1} = pd;
        varargout{2} = gn;
        varargout{3} = gl;
      endif

    case 'kernel'
      if (any (censor != 0))
        error ("fitdist: censoring is not supported for a 'Kernel' distribution.");
      endif
      if (isempty (groupvar))
        varargout{1} = prob.KernelDistribution.fit ...
                       (x, kernel, ksupport, kwidth, freq);
      else
        pd = cell (1, groups);
        for i = 1:groups
          x_i = x(g == i);
          f_i = freq(g == i);
          if (isempty (x_i))
            pd{i} = [];
            warning (msg, gn{i}, distname);
          else
            pd{i} = prob.KernelDistribution.fit ...
                    (x_i, kernel, ksupport, kwidth, f_i);
          endif
        endfor
        varargout{1} = pd;
        varargout{2} = gn;
        varargout{3} = gl;
      endif

    case 'logistic'
      if (isempty (groupvar))
        varargout{1} = prob.LogisticDistribution.fit ...
                       (x, alpha, censor, freq, options);
      else
        pd = cell (1, groups);
        for i = 1:groups
          x_i = x(g == i);
          c_i = censor(g == i);
          f_i = freq(g == i);
          if (isempty (x_i))
            pd{i} = [];
            warning (msg, gn{i}, distname);
          else
            pd{i} = prob.LogisticDistribution.fit (x_i, alpha, c_i, f_i, options);
          endif
        endfor
        varargout{1} = pd;
        varargout{2} = gn;
        varargout{3} = gl;
      endif

    case 'loglogistic'
      if (isempty (groupvar))
        varargout{1} = prob.LoglogisticDistribution.fit ...
                       (x, alpha, censor, freq, options);
      else
        pd = cell (1, groups);
        for i = 1:groups
          x_i = x(g == i);
          c_i = censor(g == i);
          f_i = freq(g == i);
          if (isempty (x_i))
            pd{i} = [];
            warning (msg, gn{i}, distname);
          else
            pd{i} = prob.LoglogisticDistribution.fit (x_i, alpha, c_i, f_i, options);
          endif
        endfor
        varargout{1} = pd;
        varargout{2} = gn;
        varargout{3} = gl;
      endif

    case 'lognormal'
      if (isempty (groupvar))
        varargout{1} = prob.LognormalDistribution.fit ...
                       (x, alpha, censor, freq, options);
      else
        pd = cell (1, groups);
        for i = 1:groups
          x_i = x(g == i);
          c_i = censor(g == i);
          f_i = freq(g == i);
          if (isempty (x_i))
            pd{i} = [];
            warning (msg, gn{i}, distname);
          else
            pd{i} = prob.LognormalDistribution.fit (x_i, alpha, c_i, f_i, options);
          endif
        endfor
        varargout{1} = pd;
        varargout{2} = gn;
        varargout{3} = gl;
      endif

    case 'nakagami'
      if (isempty (groupvar))
        varargout{1} = prob.NakagamiDistribution.fit ...
                       (x, alpha, censor, freq, options);
      else
        pd = cell (1, groups);
        for i = 1:groups
          x_i = x(g == i);
          c_i = censor(g == i);
          f_i = freq(g == i);
          if (isempty (x_i))
            pd{i} = [];
            warning (msg, gn{i}, distname);
          else
            pd{i} = prob.NakagamiDistribution.fit (x_i, alpha, c_i, f_i, options);
          endif
        endfor
        varargout{1} = pd;
        varargout{2} = gn;
        varargout{3} = gl;
      endif

    case {'negativebinomial', 'nbin'}
      if (isempty (groupvar))
        varargout{1} = prob.NegativeBinomialDistribution.fit ...
                       (x, alpha, freq, options);
      else
        pd = cell (1, groups);
        for i = 1:groups
          x_i = x(g == i);
          f_i = freq(g == i);
          if (isempty (x_i))
            pd{i} = [];
            warning (msg, gn{i}, distname);
          else
            pd{i} = prob.NegativeBinomialDistribution.fit (x_i, alpha, f_i, options);
          endif
        endfor
        varargout{1} = pd;
        varargout{2} = gn;
        varargout{3} = gl;
      endif

    case 'normal'
      if (isempty (groupvar))
        varargout{1} = prob.NormalDistribution.fit (x, alpha, censor, freq, options);
      else
        pd = cell (1, groups);
        for i = 1:groups
          x_i = x(g == i);
          c_i = censor(g == i);
          f_i = freq(g == i);
          if (isempty (x_i))
            pd{i} = [];
            warning (msg, gn{i}, distname);
          else
            pd{i} = prob.NormalDistribution.fit (x_i, alpha, c_i, f_i, options);
          endif
        endfor
        varargout{1} = pd;
        varargout{2} = gn;
        varargout{3} = gl;
      endif

    case 'poisson'
      if (isempty (groupvar))
        varargout{1} = prob.PoissonDistribution.fit (x, alpha, freq);
      else
        pd = cell (1, groups);
        for i = 1:groups
          x_i = x(g == i);
          f_i = freq(g == i);
          if (isempty (x_i))
            pd{i} = [];
            warning (msg, gn{i}, distname);
          else
            pd{i} = prob.PoissonDistribution.fit (x_i, alpha, f_i);
          endif
        endfor
        varargout{1} = pd;
        varargout{2} = gn;
        varargout{3} = gl;
      endif

    case 'rayleigh'
      if (isempty (groupvar))
        varargout{1} = prob.RayleighDistribution.fit (x, alpha, censor, freq);
      else
        pd = cell (1, groups);
        for i = 1:groups
          x_i = x(g == i);
          c_i = censor(g == i);
          f_i = freq(g == i);
          if (isempty (x_i))
            pd{i} = [];
            warning (msg, gn{i}, distname);
          else
            pd{i} = prob.RayleighDistribution.fit (x_i, alpha, c_i, f_i);
          endif
        endfor
        varargout{1} = pd;
        varargout{2} = gn;
        varargout{3} = gl;
      endif

    case 'rician'
      if (isempty (groupvar))
        varargout{1} = prob.RicianDistribution.fit (x, alpha, censor, freq, options);
      else
        pd = cell (1, groups);
        for i = 1:groups
          x_i = x(g == i);
          c_i = censor(g == i);
          f_i = freq(g == i);
          if (isempty (x_i))
            pd{i} = [];
            warning (msg, gn{i}, distname);
          else
            pd{i} = prob.RicianDistribution.fit (x_i, alpha, c_i, f_i, options);
          endif
        endfor
        varargout{1} = pd;
        varargout{2} = gn;
        varargout{3} = gl;
      endif

    case 'stable'
      if (any (censor != 0))
        error ("fitdist: censoring is not supported for a 'Stable' distribution.");
      endif
      if (isempty (groupvar))
        varargout{1} = prob.StableDistribution.fit (x, alpha, freq, options);
      else
        pd = cell (1, groups);
        for i = 1:groups
          x_i = x(g == i);
          f_i = freq(g == i);
          if (isempty (x_i))
            pd{i} = [];
            warning (msg, gn{i}, distname);
          else
            pd{i} = prob.StableDistribution.fit (x_i, alpha, f_i, options);
          endif
        endfor
        varargout{1} = pd;
        varargout{2} = gn;
        varargout{3} = gl;
      endif

    case {'tlocationscale', 'tls'}
      if (isempty (groupvar))
        varargout{1} = prob.tLocationScaleDistribution.fit ...
                       (x, alpha, censor, freq, options);
      else
        pd = cell (1, groups);
        for i = 1:groups
          x_i = x(g == i);
          c_i = censor(g == i);
          f_i = freq(g == i);
          if (isempty (x_i))
            pd{i} = [];
            warning (msg, gn{i}, distname);
          else
            pd{i} = prob.tLocationScaleDistribution.fit ...
                    (x_i, alpha, c_i, f_i, options);
          endif
        endfor
        varargout{1} = pd;
        varargout{2} = gn;
        varargout{3} = gl;
      endif

    case 'weibull'
      if (isempty (groupvar))
        varargout{1} = prob.WeibullDistribution.fit (x, alpha, censor, freq, options);
      else
        pd = cell (1, groups);
        for i = 1:groups
          x_i = x(g == i);
          c_i = censor(g == i);
          f_i = freq(g == i);
          if (isempty (x_i))
            pd{i} = [];
            warning (msg, gn{i}, distname);
          else
            pd{i} = prob.WeibullDistribution.fit (x_i, alpha, c_i, f_i, options);
          endif
        endfor
        varargout{1} = pd;
        varargout{2} = gn;
        varargout{3} = gl;
      endif

  endswitch

endfunction

## Test output
%!test  ## fitdist returns a fitted prob.KernelDistribution object
%! x = [2.1 0.3 1.2 -0.7 0.9 1.5 2.8 0.1 0.4 1.1 3.2 0.6 2.0 0.9 1.7]';
%! pd = fitdist (x, 'Kernel');
%! assert_equal (class (pd), 'prob.KernelDistribution');
%! assert_equal (pd.Kernel, 'normal');
%! assert_equal (pd.Bandwidth, 0.639566, 1e-4);
%! assert_equal (pd.InputData.data, x);
%!test  ## grouped kernel fit returns a cell of prob.KernelDistribution objects
%! x = [2.1 0.3 1.2 -0.7 0.9 1.5 2.8 0.1 0.4 1.1 3.2 0.6 2.0 0.9 1.7]';
%! [pd, gn] = fitdist (x, 'Kernel', 'By', [ones(8, 1); 2*ones(7, 1)]);
%! assert_equal (numel (pd), 2);
%! assert_equal (class (pd{1}), 'prob.KernelDistribution');
%! assert_equal (class (pd{2}), 'prob.KernelDistribution');
%!test
%! x = betarnd (1, 1, 100, 1);
%! pd = fitdist (x, 'Beta');
%! [phat, pci] = betafit (x);
%! assert_equal ([pd.a, pd.b], phat);
%! assert_equal (paramci (pd), pci);
%!test
%! x1 = betarnd (1, 1, 100, 1);
%! x2 = betarnd (5, 2, 100, 1);
%! pd = fitdist ([x1; x2], 'Beta', 'By', [ones(100, 1); 2*ones(100, 1)]);
%! [phat, pci] = betafit (x1);
%! assert_equal ([pd{1}.a, pd{1}.b], phat);
%! assert_equal (paramci (pd{1}), pci);
%! [phat, pci] = betafit (x2);
%! assert_equal ([pd{2}.a, pd{2}.b], phat);
%! assert_equal (paramci (pd{2}), pci);
%!warning <fitdist: no data in group '2' to fit a 'Beta' distribution.> ...
%! fitdist ([betarnd(1, 1, 100, 1); nan(100, 1)], 'Beta', ...
%!          'By', [ones(100, 1); 2*ones(100, 1)]);
%!test
%! N = 1;
%! x = binornd (N, 0.5, 100, 1);
%! pd = fitdist (x, 'binomial');
%! [phat, pci] = binofit (sum (x), numel (x));
%! assert_equal ([pd.N, pd.p], [N, phat]);
%! assert_equal (paramci (pd), [N, pci(1); N, pci(2)]);
%!test
%! N = 3;
%! x = binornd (N, 0.4, 100, 1);
%! pd = fitdist (x, 'binomial', 'ntrials', N);
%! [phat, pci] = binofit (sum (x), numel (x) * N);
%! assert_equal ([pd.N, pd.p], [N, phat]);
%! assert_equal (paramci (pd), [N, pci(1); N, pci(2)]);
%!test
%! N = 1;
%! x1 = binornd (N, 0.5, 100, 1);
%! x2 = binornd (N, 0.7, 100, 1);
%! pd = fitdist ([x1; x2], 'binomial', 'By', [ones(100, 1); 2*ones(100, 1)]);
%! [phat, pci] = binofit (sum (x1), numel (x1));
%! assert_equal ([pd{1}.N, pd{1}.p], [N, phat]);
%! assert_equal (paramci (pd{1}), [N, pci(1); N, pci(2)]);
%! [phat, pci] = binofit (sum (x2), numel (x2));
%! assert_equal ([pd{2}.N, pd{2}.p], [N, phat]);
%! assert_equal (paramci (pd{2}), [N, pci(1); N, pci(2)]);
%!warning <fitdist: no data in group '2' to fit a 'binomial' distribution.> ...
%! fitdist ([binornd(1, 0.5, 100, 1); nan(100, 1)], 'binomial', ...
%!          'By', [ones(100, 1); 2*ones(100, 1)]);
%!test
%! N = 5;
%! x1 = binornd (N, 0.5, 100, 1);
%! x2 = binornd (N, 0.8, 100, 1);
%! pd = fitdist ([x1; x2], 'binomial', 'ntrials', N, ...
%!               'By', [ones(100, 1); 2*ones(100, 1)]);
%! [phat, pci] = binofit (sum (x1), numel (x1) * N);
%! assert_equal ([pd{1}.N, pd{1}.p], [N, phat]);
%! assert_equal (paramci (pd{1}), [N, pci(1); N, pci(2)]);
%! [phat, pci] = binofit (sum (x2), numel (x2) * N);
%! assert_equal ([pd{2}.N, pd{2}.p], [N, phat]);
%! assert_equal (paramci (pd{2}), [N, pci(1); N, pci(2)]);
%!warning <fitdist: no data in group '2' to fit a 'binomial' distribution.> ...
%! fitdist ([binornd(5, 0.5, 100, 1); nan(100, 1)], 'binomial', 'ntrials', 5, ...
%!          'By', [ones(100, 1); 2*ones(100, 1)]);
%!test
%! x = bisarnd (1, 1, 100, 1);
%! pd = fitdist (x, 'BirnbaumSaunders');
%! [phat, pci] = bisafit (x);
%! assert_equal ([pd.beta, pd.gamma], phat);
%! assert_equal (paramci (pd), pci);
%!test
%! x1 = bisarnd (1, 1, 100, 1);
%! x2 = bisarnd (5, 2, 100, 1);
%! pd = fitdist ([x1; x2], 'bisa', 'By', [ones(100,1); 2*ones(100,1)]);
%! [phat, pci] = bisafit (x1);
%! assert_equal ([pd{1}.beta, pd{1}.gamma], phat);
%! assert_equal (paramci (pd{1}), pci);
%! [phat, pci] = bisafit (x2);
%! assert_equal ([pd{2}.beta, pd{2}.gamma], phat);
%! assert_equal (paramci (pd{2}), pci);
%!warning <fitdist: no data in group '2' to fit a 'bisa' distribution.> ...
%! fitdist ([bisarnd(1, 1, 100, 1); nan(100, 1)], 'bisa', ...
%!          'By', [ones(100, 1); 2*ones(100, 1)]);
%!test
%! x = burrrnd (1, 2, 1, 100, 1);
%! pd = fitdist (x, 'Burr');
%! [phat, pci] = burrfit (x);
%! assert_equal ([pd.alpha, pd.c, pd.k], phat);
%! assert_equal (paramci (pd), pci);
%!test
%! rand ('seed', 4);   # for reproducibility
%! x1 = burrrnd (1, 2, 1, 100, 1);
%! rand ('seed', 3);   # for reproducibility
%! x2 = burrrnd (1, 0.5, 2, 100, 1);
%! pd = fitdist ([x1; x2], 'burr', 'By', [ones(100,1); 2*ones(100,1)]);
%! [phat, pci] = burrfit (x1);
%! assert_equal ([pd{1}.alpha, pd{1}.c, pd{1}.k], phat);
%! assert_equal (paramci (pd{1}), pci);
%! [phat, pci] = burrfit (x2);
%! assert_equal ([pd{2}.alpha, pd{2}.c, pd{2}.k], phat);
%! assert_equal (paramci (pd{2}), pci);
%!warning <fitdist: no data in group '2' to fit a 'burr' distribution.> ...
%! fitdist ([burrrnd(1, 2, 1, 100, 1); nan(100, 1)], 'burr', ...
%!          'By', [ones(100, 1); 2*ones(100, 1)]);
%!test
%! x = exprnd (1, 100, 1);
%! pd = fitdist (x, 'exponential');
%! [muhat, muci] = expfit (x);
%! assert_equal ([pd.mu], muhat);
%! assert_equal (paramci (pd), muci);
%!test
%! x1 = exprnd (1, 100, 1);
%! x2 = exprnd (5, 100, 1);
%! pd = fitdist ([x1; x2], 'exponential', 'By', [ones(100,1); 2*ones(100,1)]);
%! [muhat, muci] = expfit (x1);
%! assert_equal ([pd{1}.mu], muhat);
%! assert_equal (paramci (pd{1}), muci);
%! [muhat, muci] = expfit (x2);
%! assert_equal ([pd{2}.mu], muhat);
%! assert_equal (paramci (pd{2}), muci);
%!warning <fitdist: no data in group '2' to fit a 'exponential' distribution.> ...
%! fitdist ([exprnd(1, 100, 1); nan(100, 1)], 'exponential', ...
%!          'By', [ones(100, 1); 2*ones(100, 1)]);
%!test
%! x = evrnd (1, 1, 100, 1);
%! pd = fitdist (x, 'ev');
%! [phat, pci] = evfit (x);
%! assert_equal ([pd.mu, pd.sigma], phat);
%! assert_equal (paramci (pd), pci);
%!test
%! x1 = evrnd (1, 1, 100, 1);
%! x2 = evrnd (5, 2, 100, 1);
%! pd = fitdist ([x1; x2], 'extremevalue', 'By', [ones(100,1); 2*ones(100,1)]);
%! [phat, pci] = evfit (x1);
%! assert_equal ([pd{1}.mu, pd{1}.sigma], phat);
%! assert_equal (paramci (pd{1}), pci);
%! [phat, pci] = evfit (x2);
%! assert_equal ([pd{2}.mu, pd{2}.sigma], phat);
%! assert_equal (paramci (pd{2}), pci);
%!warning <fitdist: no data in group '2' to fit a 'extremevalue' distribution.> ...
%! fitdist ([evrnd(1, 1, 100, 1); nan(100, 1)], 'extremevalue', ...
%!          'By', [ones(100, 1); 2*ones(100, 1)]);
%!test
%! x = gamrnd (1, 1, 100, 1);
%! pd = fitdist (x, 'Gamma');
%! [phat, pci] = gamfit (x);
%! assert_equal ([pd.a, pd.b], phat);
%! assert_equal (paramci (pd), pci);
%!test
%! x1 = gamrnd (1, 1, 100, 1);
%! x2 = gamrnd (5, 2, 100, 1);
%! pd = fitdist ([x1; x2], 'Gamma', 'By', [ones(100,1); 2*ones(100,1)]);
%! [phat, pci] = gamfit (x1);
%! assert_equal ([pd{1}.a, pd{1}.b], phat);
%! assert_equal (paramci (pd{1}), pci);
%! [phat, pci] = gamfit (x2);
%! assert_equal ([pd{2}.a, pd{2}.b], phat);
%! assert_equal (paramci (pd{2}), pci);
%!warning <fitdist: no data in group '2' to fit a 'Gamma' distribution.> ...
%! fitdist ([gamrnd(1, 1, 100, 1); nan(100, 1)], 'Gamma', ...
%!          'By', [ones(100, 1); 2*ones(100, 1)]);
%!test
%! rand ('seed', 4);   # for reproducibility
%! x = gevrnd (-0.5, 1, 2, 1000, 1);
%! pd = fitdist (x, 'generalizedextremevalue');
%! [phat, pci] = gevfit (x);
%! assert_equal ([pd.k, pd.sigma, pd.mu], phat);
%! assert_equal (paramci (pd), pci);
%!test
%! rand ('seed', 5);   # for reproducibility
%! x1 = gevrnd (-0.5, 1, 2, 1000, 1);
%! rand ('seed', 9);   # for reproducibility
%! x2 = gevrnd (0, 1, -4, 1000, 1);
%! pd = fitdist ([x1; x2], 'gev', 'By', [ones(1000,1); 2*ones(1000,1)]);
%! [phat, pci] = gevfit (x1);
%! assert_equal ([pd{1}.k, pd{1}.sigma, pd{1}.mu], phat);
%! assert_equal (paramci (pd{1}), pci);
%! [phat, pci] = gevfit (x2);
%! assert_equal ([pd{2}.k, pd{2}.sigma, pd{2}.mu], phat);
%! assert_equal (paramci (pd{2}), pci);
%!warning <fitdist: no data in group '2' to fit a 'gev' distribution.> ...
%! fitdist ([gevrnd(-0.5, 1, 2, 1000, 1); nan(1000, 1)], 'gev', ...
%!          'By', [ones(1000, 1); 2*ones(1000, 1)]);
%!test
%! x = gprnd (1, 1, 1, 100, 1);
%! pd = fitdist (x, 'GeneralizedPareto', 'theta', 1);
%! [phat, pci] = gpfit (x - 1);
%! assert_equal ([pd.k, pd.sigma, pd.theta], [phat, 1]);
%! assert_equal (paramci (pd), [pci, [1; 1]]);
%!test
%! x = gprnd (1, 1, 2, 100, 1);
%! pd = fitdist (x, 'GeneralizedPareto', 'theta', 2);
%! [phat, pci] = gpfit (x - 2);
%! assert_equal ([pd.k, pd.sigma, pd.theta], [phat, 2]);
%! assert_equal (paramci (pd), [pci, [2; 2]]);
%!test
%! x1 = gprnd (1, 1, 1, 100, 1);
%! x2 = gprnd (0, 2, 1, 100, 1);
%! pd = fitdist ([x1; x2], 'gp', 'theta', 1, ...
%!               'By', [ones(100,1); 2*ones(100,1)]);
%! [phat, pci] = gpfit (x1 - 1);
%! assert_equal ([pd{1}.k, pd{1}.sigma, pd{1}.theta], [phat, 1]);
%! assert_equal (paramci (pd{1}), [pci, [1; 1]]);
%! [phat, pci] = gpfit (x2 - 1);
%! assert_equal ([pd{2}.k, pd{2}.sigma, pd{2}.theta], [phat, 1]);
%! assert_equal (paramci (pd{2}), [pci, [1; 1]]);
%!warning <fitdist: no data in group '2' to fit a 'gp' distribution.> ...
%! fitdist ([gprnd(1, 1, 1, 100, 1); nan(100, 1)], 'gp', ...
%!          'By', [ones(100, 1); 2*ones(100, 1)]);
%!test
%! x1 = gprnd (3, 2, 2, 100, 1);
%! x2 = gprnd (2, 3, 2, 100, 1);
%! pd = fitdist ([x1; x2], 'GeneralizedPareto', 'theta', 2, ...
%!               'By', [ones(100,1); 2*ones(100,1)]);
%! [phat, pci] = gpfit (x1 - 2);
%! assert_equal ([pd{1}.k, pd{1}.sigma, pd{1}.theta], [phat, 2]);
%! assert_equal (paramci (pd{1}), [pci, [2; 2]]);
%! [phat, pci] = gpfit (x2 - 2);
%! assert_equal ([pd{2}.k, pd{2}.sigma, pd{2}.theta], [phat, 2]);
%! assert_equal (paramci (pd{2}), [pci, [2; 2]]);
%!warning <fitdist: no data in group '2' to fit a 'gp' distribution.> ...
%! fitdist ([gprnd(3, 2, 2, 100, 1); nan(100, 1)], 'gp', 'theta', 2, ...
%!          'By', [ones(100, 1); 2*ones(100, 1)]);
%!test
%! x = hnrnd (0, 1, 100, 1);
%! pd = fitdist (x, 'HalfNormal');
%! [phat, pci] = hnfit (x, 0);
%! assert_equal ([pd.mu, pd.sigma], phat);
%! assert_equal (paramci (pd), pci);
%!test
%! x = hnrnd (1, 1, 100, 1);
%! pd = fitdist (x, 'HalfNormal', 'mu', 1);
%! [phat, pci] = hnfit (x, 1);
%! assert_equal ([pd.mu, pd.sigma], phat);
%! assert_equal (paramci (pd), pci);
%!test
%! x1 = hnrnd (0, 1, 100, 1);
%! x2 = hnrnd (0, 2, 100, 1);
%! pd = fitdist ([x1; x2], 'HalfNormal', 'By', [ones(100,1); 2*ones(100,1)]);
%! [phat, pci] = hnfit (x1, 0);
%! assert_equal ([pd{1}.mu, pd{1}.sigma], phat);
%! assert_equal (paramci (pd{1}), pci);
%! [phat, pci] = hnfit (x2, 0);
%! assert_equal ([pd{2}.mu, pd{2}.sigma], phat);
%! assert_equal (paramci (pd{2}), pci);
%!warning <fitdist: no data in group '2' to fit a 'HalfNormal' distribution.> ...
%! fitdist ([hnrnd(0, 1, 100, 1); nan(100, 1)], 'HalfNormal', ...
%!          'By', [ones(100, 1); 2*ones(100, 1)]);
%!test
%! x1 = hnrnd (2, 1, 100, 1);
%! x2 = hnrnd (2, 2, 100, 1);
%! pd = fitdist ([x1; x2], 'HalfNormal', 'mu', 2, ...
%!               'By', [ones(100,1); 2*ones(100,1)]);
%! [phat, pci] = hnfit (x1, 2);
%! assert_equal ([pd{1}.mu, pd{1}.sigma], phat);
%! assert_equal (paramci (pd{1}), pci);
%! [phat, pci] = hnfit (x2, 2);
%! assert_equal ([pd{2}.mu, pd{2}.sigma], phat);
%! assert_equal (paramci (pd{2}), pci);
%!warning <fitdist: no data in group '2' to fit a 'HalfNormal' distribution.> ...
%! fitdist ([hnrnd(2, 1, 100, 1); nan(100, 1)], 'HalfNormal', 'mu', 2, ...
%!          'By', [ones(100, 1); 2*ones(100, 1)]);
%!test
%! x = invgrnd (1, 1, 100, 1);
%! pd = fitdist (x, 'InverseGaussian');
%! [phat, pci] = invgfit (x);
%! assert_equal ([pd.mu, pd.lambda], phat);
%! assert_equal (paramci (pd), pci);
%!test
%! x1 = invgrnd (1, 1, 100, 1);
%! x2 = invgrnd (5, 2, 100, 1);
%! pd = fitdist ([x1; x2], 'InverseGaussian', 'By', [ones(100,1); 2*ones(100,1)]);
%! [phat, pci] = invgfit (x1);
%! assert_equal ([pd{1}.mu, pd{1}.lambda], phat);
%! assert_equal (paramci (pd{1}), pci);
%! [phat, pci] = invgfit (x2);
%! assert_equal ([pd{2}.mu, pd{2}.lambda], phat);
%! assert_equal (paramci (pd{2}), pci);
%!warning <fitdist: no data in group '2' to fit a 'InverseGaussian' distribution.> ...
%! fitdist ([invgrnd(1, 1, 100, 1); nan(100, 1)], 'InverseGaussian', ...
%!          'By', [ones(100, 1); 2*ones(100, 1)]);
%!test
%! x = logirnd (1, 1, 100, 1);
%! pd = fitdist (x, 'logistic');
%! [phat, pci] = logifit (x);
%! assert_equal ([pd.mu, pd.sigma], phat);
%! assert_equal (paramci (pd), pci);
%!test
%! x1 = logirnd (1, 1, 100, 1);
%! x2 = logirnd (5, 2, 100, 1);
%! pd = fitdist ([x1; x2], 'logistic', 'By', [ones(100,1); 2*ones(100,1)]);
%! [phat, pci] = logifit (x1);
%! assert_equal ([pd{1}.mu, pd{1}.sigma], phat);
%! assert_equal (paramci (pd{1}), pci);
%! [phat, pci] = logifit (x2);
%! assert_equal ([pd{2}.mu, pd{2}.sigma], phat);
%! assert_equal (paramci (pd{2}), pci);
%!warning <fitdist: no data in group '2' to fit a 'logistic' distribution.> ...
%! fitdist ([logirnd(1, 1, 100, 1); nan(100, 1)], 'logistic', ...
%!          'By', [ones(100, 1); 2*ones(100, 1)]);
%!test
%! x = loglrnd (1, 1, 100, 1);
%! pd = fitdist (x, 'loglogistic');
%! [phat, pci] = loglfit (x);
%! assert_equal ([pd.mu, pd.sigma], phat);
%! assert_equal (paramci (pd), pci);
%!test
%! x1 = loglrnd (1, 1, 100, 1);
%! x2 = loglrnd (5, 2, 100, 1);
%! pd = fitdist ([x1; x2], 'loglogistic', 'By', [ones(100,1); 2*ones(100,1)]);
%! [phat, pci] = loglfit (x1);
%! assert_equal ([pd{1}.mu, pd{1}.sigma], phat);
%! assert_equal (paramci (pd{1}), pci);
%! [phat, pci] = loglfit (x2);
%! assert_equal ([pd{2}.mu, pd{2}.sigma], phat);
%! assert_equal (paramci (pd{2}), pci);
%!warning <fitdist: no data in group '2' to fit a 'loglogistic' distribution.> ...
%! fitdist ([loglrnd(1, 1, 100, 1); nan(100, 1)], 'loglogistic', ...
%!          'By', [ones(100, 1); 2*ones(100, 1)]);
%!test
%! x = lognrnd (1, 1, 100, 1);
%! pd = fitdist (x, 'lognormal');
%! [phat, pci] = lognfit (x);
%! assert_equal ([pd.mu, pd.sigma], phat);
%! assert_equal (paramci (pd), pci);
%!test
%! x1 = lognrnd (1, 1, 100, 1);
%! x2 = lognrnd (5, 2, 100, 1);
%! pd = fitdist ([x1; x2], 'lognormal', 'By', [ones(100,1); 2*ones(100,1)]);
%! [phat, pci] = lognfit (x1);
%! assert_equal ([pd{1}.mu, pd{1}.sigma], phat);
%! assert_equal (paramci (pd{1}), pci);
%! [phat, pci] = lognfit (x2);
%! assert_equal ([pd{2}.mu, pd{2}.sigma], phat);
%! assert_equal (paramci (pd{2}), pci);
%!warning <fitdist: no data in group '2' to fit a 'lognormal' distribution.> ...
%! fitdist ([lognrnd(1, 1, 100, 1); nan(100, 1)], 'lognormal', ...
%!          'By', [ones(100, 1); 2*ones(100, 1)]);
%!test
%! x = nakarnd (2, 0.5, 100, 1);
%! pd = fitdist (x, 'Nakagami');
%! [phat, pci] = nakafit (x);
%! assert_equal ([pd.mu, pd.omega], phat);
%! assert_equal (paramci (pd), pci);
%!test
%! x1 = nakarnd (2, 0.5, 100, 1);
%! x2 = nakarnd (5, 0.8, 100, 1);
%! pd = fitdist ([x1; x2], 'Nakagami', 'By', [ones(100,1); 2*ones(100,1)]);
%! [phat, pci] = nakafit (x1);
%! assert_equal ([pd{1}.mu, pd{1}.omega], phat);
%! assert_equal (paramci (pd{1}), pci);
%! [phat, pci] = nakafit (x2);
%! assert_equal ([pd{2}.mu, pd{2}.omega], phat);
%! assert_equal (paramci (pd{2}), pci);
%!warning <fitdist: no data in group '2' to fit a 'Nakagami' distribution.> ...
%! fitdist ([nakarnd(2, 0.5, 100, 1); nan(100, 1)], 'Nakagami', ...
%!          'By', [ones(100, 1); 2*ones(100, 1)]);
%!test
%! randp ('seed', 123);
%! randg ('seed', 321);
%! x = nbinrnd (2, 0.5, 100, 1);
%! pd = fitdist (x, 'negativebinomial');
%! [phat, pci] = nbinfit (x);
%! assert_equal ([pd.R, pd.P], phat);
%! assert_equal (paramci (pd), pci);
%!test
%! randp ('seed', 345);
%! randg ('seed', 543);
%! x1 = nbinrnd (2, 0.5, 100, 1);
%! randp ('seed', 432);
%! randg ('seed', 234);
%! x2 = nbinrnd (5, 0.8, 100, 1);
%! pd = fitdist ([x1; x2], 'nbin', 'By', [ones(100,1); 2*ones(100,1)]);
%! [phat, pci] = nbinfit (x1);
%! assert_equal ([pd{1}.R, pd{1}.P], phat);
%! assert_equal (paramci (pd{1}), pci);
%! [phat, pci] = nbinfit (x2);
%! assert_equal ([pd{2}.R, pd{2}.P], phat);
%! assert_equal (paramci (pd{2}), pci);
%!warning <fitdist: no data in group '2' to fit a 'nbin' distribution.> ...
%! fitdist ([nbinrnd(2, 0.5, 100, 1); nan(100, 1)], 'nbin', ...
%!          'By', [ones(100, 1); 2*ones(100, 1)]);
%!test
%! x = normrnd (1, 1, 100, 1);
%! pd = fitdist (x, 'normal');
%! [muhat, sigmahat, muci, sigmaci] = normfit (x);
%! assert_equal ([pd.mu, pd.sigma], [muhat, sigmahat]);
%! assert_equal (paramci (pd), [muci, sigmaci]);
%!test
%! x1 = normrnd (1, 1, 100, 1);
%! x2 = normrnd (5, 2, 100, 1);
%! pd = fitdist ([x1; x2], 'normal', 'By', [ones(100,1); 2*ones(100,1)]);
%! [muhat, sigmahat, muci, sigmaci] = normfit (x1);
%! assert_equal ([pd{1}.mu, pd{1}.sigma], [muhat, sigmahat]);
%! assert_equal (paramci (pd{1}), [muci, sigmaci]);
%! [muhat, sigmahat, muci, sigmaci] = normfit (x2);
%! assert_equal ([pd{2}.mu, pd{2}.sigma], [muhat, sigmahat]);
%! assert_equal (paramci (pd{2}), [muci, sigmaci]);
%!warning <fitdist: no data in group '2' to fit a 'normal' distribution.> ...
%! fitdist ([normrnd(1, 1, 100, 1); nan(100, 1)], 'normal', ...
%!          'By', [ones(100, 1); 2*ones(100, 1)]);
%!test
%! x = poissrnd (1, 100, 1);
%! pd = fitdist (x, 'poisson');
%! [phat, pci] = poissfit (x);
%! assert_equal (pd.lambda, phat);
%! assert_equal (paramci (pd), pci);
%!test
%! x1 = poissrnd (1, 100, 1);
%! x2 = poissrnd (5, 100, 1);
%! pd = fitdist ([x1; x2], 'poisson', 'By', [ones(100,1); 2*ones(100,1)]);
%! [phat, pci] = poissfit (x1);
%! assert_equal (pd{1}.lambda, phat);
%! assert_equal (paramci (pd{1}), pci);
%! [phat, pci] = poissfit (x2);
%! assert_equal (pd{2}.lambda, phat);
%! assert_equal (paramci (pd{2}), pci);
%!warning <fitdist: no data in group '2' to fit a 'poisson' distribution.> ...
%! fitdist ([poissrnd(1, 100, 1); nan(100, 1)], 'poisson', ...
%!          'By', [ones(100, 1); 2*ones(100, 1)]);
%!test
%! x = raylrnd (1, 100, 1);
%! pd = fitdist (x, 'rayleigh');
%! [phat, pci] = raylfit (x);
%! assert_equal (pd.B, phat);
%! assert_equal (paramci (pd), pci);
%!test
%! x1 = raylrnd (1, 100, 1);
%! x2 = raylrnd (5, 100, 1);
%! pd = fitdist ([x1; x2], 'rayleigh', 'By', [ones(100,1); 2*ones(100,1)]);
%! [phat, pci] = raylfit (x1);
%! assert_equal (pd{1}.B, phat);
%! assert_equal (paramci (pd{1}), pci);
%! [phat, pci] = raylfit (x2);
%! assert_equal (pd{2}.B, phat);
%! assert_equal (paramci (pd{2}), pci);
%!warning <fitdist: no data in group '2' to fit a 'rayleigh' distribution.> ...
%! fitdist ([raylrnd(1, 100, 1); nan(100, 1)], 'rayleigh', ...
%!          'By', [ones(100, 1); 2*ones(100, 1)]);
%!test
%! x = ricernd (1, 1, 100, 1);
%! pd = fitdist (x, 'rician');
%! [phat, pci] = ricefit (x);
%! assert_equal ([pd.s, pd.sigma], phat);
%! assert_equal (paramci (pd), pci);
%!test
%! x1 = ricernd (1, 1, 100, 1);
%! x2 = ricernd (5, 2, 100, 1);
%! pd = fitdist ([x1; x2], 'rician', 'By', [ones(100,1); 2*ones(100,1)]);
%! [phat, pci] = ricefit (x1);
%! assert_equal ([pd{1}.s, pd{1}.sigma], phat);
%! assert_equal (paramci (pd{1}), pci);
%! [phat, pci] = ricefit (x2);
%! assert_equal ([pd{2}.s, pd{2}.sigma], phat);
%! assert_equal (paramci (pd{2}), pci);
%!warning <fitdist: no data in group '2' to fit a 'rician' distribution.> ...
%! fitdist ([ricernd(1, 1, 100, 1); nan(100, 1)], 'rician', ...
%!          'By', [ones(100, 1); 2*ones(100, 1)]);
%!test  ## fitdist returns a fitted prob.StableDistribution object
%! rand ("seed", 2718);
%! randn ("seed", 2718);
%! x = stblrnd (1.5, 0.5, 2, 1, 150, 1);
%! pd = fitdist (x, 'Stable');
%! assert_equal (class (pd), 'prob.StableDistribution');
%! assert_equal (! any (pd.ParameterIsFixed, 'all'), true);
%! assert_equal (pd.alpha, 1.5, 0.4);
%! assert_equal (pd.gam, 2, 0.5);
%!test
%! x = tlsrnd (0, 1, 1, 100, 1);
%! pd = fitdist (x, 'tlocationscale');
%! [phat, pci] = tlsfit (x);
%! assert_equal ([pd.mu, pd.sigma, pd.nu], phat);
%! assert_equal (paramci (pd), pci);
%!test
%! x1 = tlsrnd (0, 1, 1, 100, 1);
%! x2 = tlsrnd (5, 2, 1, 100, 1);
%! pd = fitdist ([x1; x2], 'tlocationscale', 'By', [ones(100,1); 2*ones(100,1)]);
%! [phat, pci] = tlsfit (x1);
%! assert_equal ([pd{1}.mu, pd{1}.sigma, pd{1}.nu], phat);
%! assert_equal (paramci (pd{1}), pci);
%! [phat, pci] = tlsfit (x2);
%! assert_equal ([pd{2}.mu, pd{2}.sigma, pd{2}.nu], phat);
%! assert_equal (paramci (pd{2}), pci);
%!warning <fitdist: no data in group '2' to fit a 'tlocationscale' distribution.> ...
%! fitdist ([tlsrnd(0, 1, 1, 100, 1); nan(100, 1)], 'tlocationscale', ...
%!          'By', [ones(100, 1); 2*ones(100, 1)]);
%!test
%! x = [1 2 3 4 5];
%! pd = fitdist (x, 'weibull');
%! [phat, pci] = wblfit (x);
%! assert_equal ([pd.A, pd.B], phat);
%! assert_equal (paramci (pd), pci);
%!test
%! x = [1 2 3 4 5 6 7 8 9 10];
%! pd = fitdist (x, 'weibull', 'By', [1 1 1 1 1 2 2 2 2 2]);
%! [phat, pci] = wblfit (x(1:5));
%! assert_equal ([pd{1}.A, pd{1}.B], phat);
%! assert_equal (paramci (pd{1}), pci);
%! [phat, pci] = wblfit (x(6:10));
%! assert_equal ([pd{2}.A, pd{2}.B], phat);
%! assert_equal (paramci (pd{2}), pci);
%!warning <fitdist: no data in group '2' to fit a 'weibull' distribution.> ...
%! fitdist ([1 2 3 4 5 NaN NaN NaN NaN NaN], 'weibull', 'By', [1 1 1 1 1 2 2 2 2 2]);

## Test input validation
%!error <fitdist: DISTNAME is required.> fitdist (1)
%!error <fitdist: DISTNAME must be a character vector.> fitdist (1, ['as';'sd'])
%!error <fitdist: unrecognized distribution name.> fitdist (1, 'some')
%!error <fitdist: X must be a numeric vector of real values.> ...
%! fitdist (ones (2), 'normal')
%!error <fitdist: X must be a numeric vector of real values.> ...
%! fitdist ([i, 2, 3], 'normal')
%!error <fitdist: X must be a numeric vector of real values.> ...
%! fitdist (['a', 's', 'd'], 'normal')
%!error <fitdist: optional arguments must be in NAME-VALUE pairs.> ...
%! fitdist ([1, 2, 3], 'normal', 'By')
%!error <fitdist: GROUPVAR argument must have the same size as the input data in X.> ...
%! fitdist ([1, 2, 3], 'normal', 'By', [1, 2])
%!error <fitdist: 'censoring' argument must have the same size as the input data in X.> ...
%! fitdist ([1, 2, 3], 'normal', 'Censoring', [1, 2])
%!error <fitdist: 'frequency' argument must have the same size as the input data in X.> ...
%! fitdist ([1, 2, 3], 'normal', 'frequency', [1, 2])
%!error <fitdist: 'frequency' argument must contain non-negative integer values.> ...
%! fitdist ([1, 2, 3], 'negativebinomial', 'frequency', [1, -2, 3])
%!error <fitdist: invalid value for 'alpha' argument.> ...
%! fitdist ([1, 2, 3], 'normal', 'alpha', [1, 2])
%!error <fitdist: invalid value for 'alpha' argument.> ...
%! fitdist ([1, 2, 3], 'normal', 'alpha', i)
%!error <fitdist: invalid value for 'alpha' argument.> ...
%! fitdist ([1, 2, 3], 'normal', 'alpha', -0.5)
%!error <fitdist: invalid value for 'alpha' argument.> ...
%! fitdist ([1, 2, 3], 'normal', 'alpha', 1.5)
%!error <fitdist: 'ntrials' argument must be a positive integer scalar value.> ...
%! fitdist ([1, 2, 3], 'normal', 'ntrials', [1, 2])
%!error <fitdist: 'ntrials' argument must be a positive integer scalar value.> ...
%! fitdist ([1, 2, 3], 'normal', 'ntrials', 0)
%!error <fitdist: 'options' argument must be a structure compatible for 'fminsearch'.> ...
%! fitdist ([1, 2, 3], 'normal', 'options', 0)
%!error <fitdist: 'options' argument must be a structure compatible for 'fminsearch'.> ...
%! fitdist ([1, 2, 3], 'normal', 'options', struct ('options', 1))
%!error <fitdist: censoring is not supported for a 'Kernel' distribution.> ...
%! fitdist ([1, 2, 3]', 'kernel', 'Censoring', [1, 0, 0]');
%!error <fitdist: censoring is not supported for a 'Stable' distribution.> ...
%! fitdist ([1, 2, 3]', 'Stable', 'Censoring', [1, 0, 0]');
%!error <fitdist: unknown parameter name.> ...
%! fitdist ([1, 2, 3], 'normal', 'param', struct ('options', 1))
%!error <fitdist: no data in X to fit a 'normal' distribution.> ...
%! fitdist (nan (100,1), 'normal');
%!error <fitdist: must define GROUPVAR for more than one output arguments.> ...
%! [pdca, gn, gl] = fitdist ([1, 2, 3], 'normal');
%!error <fitdist: invalid THETA value for generalized Pareto distribution.> ...
%! fitdist ([1, 2, 3], 'generalizedpareto', 'theta', 2);
%!error <fitdist: invalid MU value for half-normal distribution.> ...
%! fitdist ([1, 2, 3], 'halfnormal', 'mu', 2);
