Generated reference › API — ICoreBlocks/ICoreMath/Statistics
kind: generated#api#icoreblocks-icoremath-statistics

API — ICoreBlocks/ICoreMath/Statistics

The public contract of 18 header(s) under src/ICoreBlocks/ICoreMath/Statistics — 18 class/struct definition(s), 24 declaration(s). Each section shows the header's banner and its public (and protected-virtual) surface exactly as the file writes it.

ICoreClassificationScoring.h#

src/ICoreBlocks/ICoreMath/Statistics/ICoreClassificationScoring.h

ICoreClassificationScoring#

ICoreClassificationScoring.h:41 · class · 2 declaration(s)

Toolbox row T3.21 -- classify, confusionmat and perfcurve: the one classifier the Statistics Toolbox ships WITHOUT an object, and the two functions that score any classifier's output.

class ICoreClassificationScoring {
public:
    // MATLAB's five discriminant types.
    enum class Discriminant { Linear, Quadratic, DiagLinear, DiagQuadratic, Mahalanobis };

    static bool discriminantOf(const std::string& word, Discriminant& type);

    // `[class, err, posterior, logp] = classify(sample, training, group[, type[, prior]])`.
    // `prior` may be empty for MATLAB's default (equal over the non-empty
    // groups); `posterior` and `logDensity` come back empty for the
    // Mahalanobis type, which is what MATLAB answers.
    //
    // `group` is numeric here: MATLAB's grouping variable may also be a cell
    // of names, and this console has no cell kind (Z3). The group LEVELS are
    // the distinct values sorted ascending, which is `grp2idx`'s own order.
    static bool discriminantClassify(const ICoreMatrix& sample, const ICoreMatrix& training,
                                     const ICoreMatrix& group, const Discriminant& type,
                                     const ICoreMatrix& prior, ICoreMatrix& classes,
                                     double& resubstitutionError, ICoreMatrix& posterior,
                                     ICoreMatrix& logDensity, std::string* whyNot = nullptr);

    // `[C, order] = confusionmat(actual, predicted[, "Order", order])`. `order`
    // in is empty for the default; `groups` out is the list the rows and
    // columns are in.
    static bool confusionMatrix(const ICoreMatrix& actual, const ICoreMatrix& predicted,
                                const ICoreMatrix& order, ICoreMatrix& counts,
                                ICoreMatrix& groups, std::string* whyNot = nullptr);

    // The performance criteria `perfcurve` can put on either axis. Each is a
    // ratio of the four counts at one threshold, and the names are MATLAB's
    // own (with its synonyms: `sens`, `reca` and `tpr` are one criterion).
    enum class Criterion { TruePositiveRate, FalsePositiveRate, FalseNegativeRate,
                           TrueNegativeRate, Precision, NegativePredictiveValue, Accuracy };

    static bool criterionOf(const std::string& word, Criterion& criterion);

    // `[X, Y, T, AUC] = perfcurve(labels, scores, posclass[, "XCrit", x, "YCrit", y])`.
    //
    // The area is the trapezoid rule over the curve as returned, with any
    // segment whose end is undefined (a precision at zero predicted positives)
    // left out -- which is how MATLAB answers a number for a curve that starts
    // at NaN.
    static bool performanceCurve(const ICoreMatrix& labels, const ICoreMatrix& scores,
                                 const double& positiveClass, const Criterion& horizontal,
                                 const Criterion& vertical, ICoreMatrix& x, ICoreMatrix& y,
                                 ICoreMatrix& thresholds, double& area,
                                 std::string* whyNot = nullptr);

};
};

ICoreClusterAnalysis.h#

src/ICoreBlocks/ICoreMath/Statistics/ICoreClusterAnalysis.h

ICoreClusterAnalysis#

ICoreClusterAnalysis.h:55 · class · 3 declaration(s)

The clustering zone: the hierarchical block of toolbox row T3.20 -- linkage, cluster, clusterdata, cophenet, inconsistent and silhouette -- and T3.18's kmeans, at the bottom of the file.

class ICoreClusterAnalysis {
public:
    // MATLAB's seven method words. `Ward` is spelled `ward` on the console the
    // way MATLAB spells it, though `linkage.m`'s own table writes it `ward's`.
    enum class Method { Single, Complete, Average, Weighted, Centroid, Median, Ward };
    static bool methodOf(const std::string& word, Method& method);

    // Whether this method's tree is re-sorted by `rearrange` -- true for
    // average, weighted, complete and Ward's, false for the other three.
    static bool methodIsRearranged(const Method& method);

    // `linkage(Y[, method])` over a CONDENSED distance row, T3.19's `pdist`
    // order. Answers the (m-1) x 3 tree.
    static bool linkageOfDistances(const ICoreMatrix& condensed, const Method& method,
                                   ICoreMatrix& tree, std::string* whyNot = nullptr);

    // `linkage(X[, method[, metric]])` over a data matrix: `pdist` first, then
    // the tree. `metric` is one of T3.19's twelve words.
    static bool linkageOfPoints(const ICoreMatrix& points, const Method& method,
                                const std::string& metric, const double& exponent,
                                ICoreMatrix& tree, std::string* whyNot = nullptr);

    // `inconsistent(Z[, depth])` -- the (m-1) x 4 table: the mean and the
    // standard deviation of the link heights in the sub-tree `depth` levels
    // below each link, how many links that was, and the inconsistency
    // coefficient itself.
    //
    // The standard deviation divides by n-1 EXCEPT when n is 1, where it
    // divides by 1 and answers zero -- `inconsistent.m`'s `s(3)-(s(3)~=1)`,
    // which is not the same as MATLAB's `std` and would be a NaN if it were.
    static bool inconsistentTable(const ICoreMatrix& tree, const size_t& depth,
                                  ICoreMatrix& out, std::string* whyNot = nullptr);

    // Which criterion a cut is measured against.
    enum class Criterion { Inconsistent, Distance };

    // `cluster(Z, "Cutoff", c[, "Criterion", crit][, "Depth", d])` -- every
    // link at or below the cutoff stays connected, and a link counts as
    // connected only if every link beneath it is too (`checkcut` in
    // cluster.m). Answers one cluster number per leaf, renumbered by first
    // appearance.
    static bool clusterByCutoff(const ICoreMatrix& tree, const double& cutoff,
                                const Criterion& criterion, const size_t& depth,
                                ICoreMatrix& out, std::string* whyNot = nullptr);

    // `cluster(Z, "MaxClust", k)` -- at most k clusters.
    //
    // ⚠ THE DEFAULT CRITERION CHANGES WITH THE FORM. `cluster(Z, 3)` and
    // `cluster(Z, "MaxClust", 3)` cut by DISTANCE, not by inconsistency, even
    // though "inconsistent" is the documented default: `cluster.m` overrides
    // it whenever a cutoff was not given and the criterion was not named. The
    // inconsistency-based count form is reachable only by naming the
    // criterion, and is what `inconsistentCount` is.
    static bool clusterByCount(const ICoreMatrix& tree, const size_t& maxClusters,
                               ICoreMatrix& out, std::string* whyNot = nullptr);

    // `cluster(Z, "MaxClust", k, "Criterion", "inconsistent"[, "Depth", d])`:
    // the largest inconsistency cutoff that still leaves at most k clusters,
    // found by walking the distinct coefficients downward.
    static bool clusterByInconsistentCount(const ICoreMatrix& tree, const size_t& maxClusters,
                                           const size_t& depth, ICoreMatrix& out,
                                           std::string* whyNot = nullptr);

    // `cophenet(Z, Y)` -- the cophenetic correlation coefficient, and the
    // cophenetic distances themselves in `pdist` order.
    static bool cophenetic(const ICoreMatrix& tree, const ICoreMatrix& condensed,
                           double& coefficient, ICoreMatrix& distances,
                           std::string* whyNot = nullptr);

    // `silhouette(X, idx[, metric])` -- one value per row of X.
    //
    // ⚠ THE DEFAULT METRIC IS SQUARED EUCLIDEAN, not Euclidean. `silhouette.m`
    // says `distType = 'sqeuclidean'` when no metric is given, which is the
    // one place in the Statistics toolbox where a squared distance is the
    // default, and it moves every number.
    //
    // A point alone in its cluster has no within-cluster distance to average.
    // MATLAB answers its silhouette as exactly 1 -- measured against R2026a,
    // `silhouette([0 0; 1 1; 10 10], [1;1;2])` ends in a 1 -- because the
    // average it cannot take comes out 0 and `(b - 0)/max(0, b)` is 1. Not a
    // NaN, and not the 0 a "no information" reading would give.
    static bool silhouetteValues(const ICoreMatrix& points, const ICoreMatrix& assignment,
                                 const std::string& metric, const double& exponent,
                                 ICoreMatrix& out, std::string* whyNot = nullptr);

    // ---- k-means, toolbox row T3.18 ---------------------------------------
    //
    // Only the arm that is DETERMINED: `kmeans(X, k, "Start", C0)` with the
    // starting centres given as a matrix. Without them MATLAB picks its start
    // from its own random stream (`"plus"`, k-means++) and no seed on this
    // console reproduces the draw, so that form is refused rather than
    // answered with a number nothing can compare (board rule Z11).
    //
    // Its five metric words are not five distance functions with a shared
    // centre; each word carries its OWN centre, and that is the half of
    // `kmeans` a reader is most likely to assume away:
    //
    //   sqeuclidean  squared distance, centre = the MEAN
    //   cityblock    absolute distance, centre = the MEDIAN (the even case
    //                averages the two middle values, so a centre need not be
    //                one of the points)
    //   cosine       1 - the cosine of the angle, on rows normalized to unit
    //                length first; the centre is the UNNORMALIZED mean of the
    //                normalized rows, re-normalized only when it is measured
    //                against
    //   correlation  the same, on rows centred on their own mean first
    //   hamming      the fraction of coordinates that differ, on 0/1 data;
    //                centre = the MAJORITY, `2*sum > count`, so a tie goes to
    //                zero
    enum class Centre { SquaredEuclidean, CityBlock, Cosine, Correlation, Hamming };
    static bool centreMetricOf(const std::string& word, Centre& metric);

    // `[idx, C, sumd, D] = kmeans(X, k, "Start", C0[, "Distance", d]
    //                             [, "MaxIter", n])`.
    //
    // Two rules decide the answer where a reimplementation would guess:
    // a point moves to another cluster only when the new distance is STRICTLY
    // smaller (so a tie leaves it where it is), and `min` takes the FIRST of
    // several equal distances (so a first assignment ties to the lower cluster
    // number). An empty cluster is refilled with the single loneliest point,
    // taken out of the cluster it was in -- MATLAB's `"singleton"` action,
    // which is its default and the only one carried here.
    static bool kMeans(const ICoreMatrix& points, const ICoreMatrix& start, const Centre& metric,
                       const size_t& maxIterations, ICoreMatrix& assignment, ICoreMatrix& centres,
                       ICoreMatrix& withinSums, ICoreMatrix& distances,
                       std::string* whyNot = nullptr);

    // `[idx, C, sumd, D, midx] = kmedoids(X, k, "Start", C0[, "Distance", d])`
    // -- the same question with the centre required to BE one of the points.
    //
    // ⚠ Its starting rows must be rows OF X, which `kmeans` does not require
    // and MATLAB enforces with its own sentence: a medoid that is not a data
    // point is not a medoid. `midx` is which rows they are.
    //
    // The swap phase of PAM, from the given start: every (medoid, non-medoid)
    // exchange is costed, the best improving one is taken, and it repeats
    // until none improves. That is deterministic given the start, which is why
    // this arm is comparable and the unstarted one is not. `start` here is the
    // metric's own distance -- squared Euclidean by default, which is what
    // makes `sumd` a sum of SQUARED distances.
    static bool kMedoids(const ICoreMatrix& points, const ICoreMatrix& start, const Centre& metric,
                         const size_t& maxIterations, ICoreMatrix& assignment,
                         ICoreMatrix& centres, ICoreMatrix& withinSums, ICoreMatrix& distances,
                         ICoreMatrix& medoidRows, std::string* whyNot = nullptr);

};
};

ICoreCorrelationAnalysis.h#

src/ICoreBlocks/ICoreMath/Statistics/ICoreCorrelationAnalysis.h

ICoreCorrelationAnalysis#

ICoreCorrelationAnalysis.h:49 · class · 3 declaration(s)

The correlation block of toolbox row T3.16 -- corr, partialcorr, corrcov and tiedrank.

class ICoreCorrelationAnalysis {
public:
    enum class Type { Pearson, Spearman, Kendall };
    enum class Rows { All, Complete, Pairwise };
    enum class Tail { Both, Right, Left };

    static bool typeOf(const std::string& word, Type& type);
    static bool rowsOf(const std::string& word, Rows& rows);
    static bool tailOf(const std::string& word, Tail& tail);

    // `[rho, pval] = corr(X[, Y])`. `y` may be empty for the one-argument
    // form, which answers the symmetric matrix with exact ones on its
    // diagonal (MATLAB puts them there rather than letting the arithmetic
    // decide). `wantP` asks for the p-value and is refused for the two rank
    // types, for the reason in the class comment.
    static bool correlation(const ICoreMatrix& x, const ICoreMatrix* y, const Type& type,
                            const Rows& rows, const Tail& tail, const bool& wantP,
                            ICoreMatrix& rho, ICoreMatrix& pValue,
                            std::string* whyNot = nullptr);

    // `[rho, pval] = partialcorr(X[, Y], Z)` -- the correlation of what is
    // left of X (and Y) after regressing each column on `[1 Z]`. The p-value
    // is the same t transform with `n - rank(Z) - 2` degrees of freedom.
    static bool partialCorrelation(const ICoreMatrix& x, const ICoreMatrix* y,
                                   const ICoreMatrix& z, const Type& type, const Tail& tail,
                                   const bool& wantP, ICoreMatrix& rho, ICoreMatrix& pValue,
                                   std::string* whyNot = nullptr);

    // `[R, sigma] = corrcov(C)` -- the correlation matrix of a covariance
    // matrix, and the standard deviations it was scaled by.
    static bool correlationFromCovariance(const ICoreMatrix& c, ICoreMatrix& r,
                                          ICoreMatrix& sigma, std::string* whyNot = nullptr);

    // `[r, tieadj] = tiedrank(x)` -- ranks down each column, ties averaged.
    // `tieadj` is the adjustment `sum((t^3 - t) / 2)` over the tie groups,
    // which is what `signrank` and `ranksum` need and is 0 when nothing ties.
    static bool tiedRank(const ICoreMatrix& x, ICoreMatrix& ranks, double& tieAdjustment,
                         std::string* whyNot = nullptr);

};
};

ICoreDescriptiveStatistics.h#

src/ICoreBlocks/ICoreMath/Statistics/ICoreDescriptiveStatistics.h

ICoreDescriptiveStatistics#

ICoreDescriptiveStatistics.h:52 · class · 0 declaration(s)

The nine descriptive statistics of toolbox row T3.1 -- prctile, quantile, iqr, range, mad, zscore, skewness, kurtosis and moment.

class ICoreDescriptiveStatistics {
public:
    // `prctile(x, p)` with p in PERCENT, and `quantile(x, p)` with p in [0, 1]
    // -- one function because quantile.m is one line calling prctile with
    // `100.*p`. `p` may be a vector, and the answer follows MATLAB's shape:
    // a vector `x` answers p's shape, a matrix answers one row per percentile.
    //
    // `asQuantile` also carries quantile's own integer form: a scalar whole
    // `p > 1` means "that many evenly spaced quantiles", `(1:p)/(p+1)`.
    static ICoreMatrix percentile(const ICoreMatrix& x, const ICoreMatrix& p,
                                  const int& dim, const bool& asQuantile,
                                  std::string* whyNot = nullptr);

    // `iqr(x)` -- the 75th percentile less the 25th, through the rule above,
    // so it inherits the clamping and the (i-0.5)/n placement.
    static ICoreMatrix interquartileRange(const ICoreMatrix& x, const int& dim,
                                          std::string* whyNot = nullptr);

    // `range(x)` -- max less min. Statistics Toolbox, though it is two core
    // calls; the console keeps `max(A) - min(A)` as the core spelling.
    static ICoreMatrix range(const ICoreMatrix& x, const int& dim,
                             std::string* whyNot = nullptr);

    // `mad(x, flag)`. `useMedian` is MATLAB's `flag == 1`.
    static ICoreMatrix absoluteDeviation(const ICoreMatrix& x, const bool& useMedian,
                                         const int& dim, std::string* whyNot = nullptr);

    // `[z, mu, sigma] = zscore(x, flag, dim)`. `population` is flag == 1 (N);
    // the default is flag == 0 (N-1).
    static bool standardScore(const ICoreMatrix& x, const bool& population, const int& dim,
                              ICoreMatrix& z, ICoreMatrix& mu, ICoreMatrix& sigma,
                              std::string* whyNot = nullptr);

    // `skewness(x, flag)` and `kurtosis(x, flag)`. `biased` is flag == 1, and
    // it is the DEFAULT. The unbiased correction is undefined below n = 3
    // (skewness) and n = 4 (kurtosis) and answers NaN there, as MATLAB does.
    static ICoreMatrix skewness(const ICoreMatrix& x, const bool& biased, const int& dim,
                                std::string* whyNot = nullptr);
    static ICoreMatrix kurtosis(const ICoreMatrix& x, const bool& biased, const int& dim,
                                std::string* whyNot = nullptr);

    // `moment(x, order)` -- the CENTRAL moment, always over the biased mean.
    // Order 1 is exactly 0 by definition and MATLAB returns it without
    // computing anything, which is why it is 0 and not 1e-17.
    static ICoreMatrix centralMoment(const ICoreMatrix& x, const double& order, const int& dim,
                                     std::string* whyNot = nullptr);

};
};

ICoreDistanceMetrics.h#

src/ICoreBlocks/ICoreMath/Statistics/ICoreDistanceMetrics.h

ICoreDistanceMetrics#

ICoreDistanceMetrics.h:45 · class · 2 declaration(s)

The distance block of toolbox row T3.19 -- pdist, pdist2, squareform, mahal and knnsearch, over twelve of MATLAB's distance metrics.

class ICoreDistanceMetrics {
public:
    // MATLAB's twelve metric words. `Jaccard` and `Hamming` read their inputs
    // as patterns rather than numbers, which is why they are here rather than
    // refused: MATLAB defines both on any numeric data.
    enum class Metric {
        Euclidean, SquaredEuclidean, StandardizedEuclidean, CityBlock, Minkowski,
        Chebychev, Cosine, Correlation, Hamming, Jaccard, Spearman, Mahalanobis
    };

    // The metric word, MATLAB's spellings (including "chebychev", which is
    // MATLAB's spelling and not the more common "chebyshev").
    static bool metricOf(const std::string& word, Metric& metric);

    // `pdist(X[, metric[, arg]])` -- the CONDENSED 1 x n(n-1)/2 row.
    // `exponent` is Minkowski's p and is ignored by every other metric.
    static ICoreMatrix pairwise(const ICoreMatrix& x, const Metric& metric,
                                const double& exponent, std::string* whyNot = nullptr);

    // `pdist2(X, Y[, metric[, arg]])` -- the FULL nx x ny matrix. The scale
    // (standardized Euclidean) and the covariance (Mahalanobis) come from X
    // alone, which is MATLAB's default and is measurable rather than
    // documented: pdist2(X, Y, "seuclidean") is pdist's own weights.
    static ICoreMatrix pairwiseBetween(const ICoreMatrix& x, const ICoreMatrix& y,
                                       const Metric& metric, const double& exponent,
                                       std::string* whyNot = nullptr);

    // `squareform(v)` and `squareform(D)`, the direction taken from the shape
    // exactly as squareform.m takes it: a VECTOR becomes the symmetric matrix,
    // a matrix becomes the vector, and a matrix with a non-zero diagonal is an
    // error rather than a matrix with its diagonal ignored.
    static ICoreMatrix squareForm(const ICoreMatrix& in, std::string* whyNot = nullptr);

    // `mahal(Y, X)` -- the SQUARED Mahalanobis distance of each row of Y from
    // the sample X, one per row of Y. Not the same number as
    // `pdist2(Y, mean(X), "mahalanobis")`, which is its square root.
    static ICoreMatrix mahalanobis(const ICoreMatrix& y, const ICoreMatrix& x,
                                   std::string* whyNot = nullptr);

    // `[idx, D] = knnsearch(X, Y[, "K", k][, "Distance", metric])` -- the k
    // nearest rows of X to each row of Y, nearest first, as a ny x k index
    // matrix (1-based, MATLAB's) and the matching distances.
    //
    // TIES GO TO THE SMALLER INDEX. Measured against R2026a rather than
    // assumed: knnsearch([1 0; 0 1; 5 5], [0 0], "K", 2) answers [1 2], and
    // MATLAB's own documentation only promises "one of them".
    static bool nearestNeighbours(const ICoreMatrix& x, const ICoreMatrix& y, const size_t& k,
                                  const Metric& metric, const double& exponent,
                                  ICoreMatrix& indices, ICoreMatrix& distances,
                                  std::string* whyNot = nullptr);

};
};

ICoreDistributions.h#

src/ICoreBlocks/ICoreMath/Statistics/ICoreDistributions.h

ICoreDistributions#

ICoreDistributions.h:51 · class · nested FamilyShape · 1 declaration(s)

The normal family and the t / chi-square / F families (toolbox rows T3.3 and T3.5): the distribution FUNCTIONS, which take numbers and answer numbers.

class ICoreDistributions {
public:
    // Whether an answer is the lower tail or MATLAB's "upper" one.
    enum class Tail { Lower, Upper };

    // ---- the normal family (T3.3) ----------------------------------------
    //
    // `x`, `mu` and `sigma` broadcast the way MATLAB's `distchck` broadcasts:
    // each is either a scalar or the common shape. A `sigma` at or below zero
    // answers NaN rather than refusing, which is MATLAB's choice throughout
    // this family.
    static bool normalPdf(const ICoreMatrix& x, const ICoreMatrix& mean,
                          const ICoreMatrix& deviation, ICoreMatrix& out,
                          std::string* whyNot = nullptr);
    static bool normalCdf(const ICoreMatrix& x, const ICoreMatrix& mean,
                          const ICoreMatrix& deviation, const Tail& tail, ICoreMatrix& out,
                          std::string* whyNot = nullptr);
    static bool normalInv(const ICoreMatrix& p, const ICoreMatrix& mean,
                          const ICoreMatrix& deviation, ICoreMatrix& out,
                          std::string* whyNot = nullptr);
    // MATLAB's [m, v] = normstat(mu, sigma): the mean and the VARIANCE, so v
    // is sigma^2 and not sigma.
    static bool normalStat(const ICoreMatrix& mean, const ICoreMatrix& deviation,
                           ICoreMatrix& outMean, ICoreMatrix& outVariance,
                           std::string* whyNot = nullptr);
    // MATLAB's nlogL = normlike([mu sigma], data): the NEGATIVE log
    // likelihood, so a better fit is a smaller number.
    static bool normalLike(const ICoreMatrix& parameters, const ICoreMatrix& data,
                           double& negativeLogLikelihood, std::string* whyNot = nullptr);
    // MATLAB's [muhat, sigmahat, muci, sigmaci] = normfit(x[, alpha]).
    //
    // `sigmahat` is the UNBIASED deviation (divided by n - 1), and the two
    // intervals are the exact ones rather than a normal approximation: the
    // mean's is a t interval and the deviation's a chi-square interval, which
    // is why this row and T3.5 were claimed together -- `normfit` cannot be
    // finished without `tinv` and `chi2inv`.
    static bool normalFit(const ICoreMatrix& x, const double& alpha, double& meanEstimate,
                          double& deviationEstimate, ICoreMatrix& meanInterval,
                          ICoreMatrix& deviationInterval, std::string* whyNot = nullptr);

    // ---- the t, chi-square and F families (T3.5) -------------------------
    static bool studentPdf(const ICoreMatrix& x, const ICoreMatrix& degrees, ICoreMatrix& out,
                           std::string* whyNot = nullptr);
    static bool studentCdf(const ICoreMatrix& x, const ICoreMatrix& degrees, const Tail& tail,
                           ICoreMatrix& out, std::string* whyNot = nullptr);
    static bool studentInv(const ICoreMatrix& p, const ICoreMatrix& degrees, ICoreMatrix& out,
                           std::string* whyNot = nullptr);
    static bool chiSquarePdf(const ICoreMatrix& x, const ICoreMatrix& degrees, ICoreMatrix& out,
                             std::string* whyNot = nullptr);
    static bool chiSquareCdf(const ICoreMatrix& x, const ICoreMatrix& degrees, const Tail& tail,
                             ICoreMatrix& out, std::string* whyNot = nullptr);
    static bool chiSquareInv(const ICoreMatrix& p, const ICoreMatrix& degrees, ICoreMatrix& out,
                             std::string* whyNot = nullptr);
    static bool fisherPdf(const ICoreMatrix& x, const ICoreMatrix& first,
                          const ICoreMatrix& second, ICoreMatrix& out,
                          std::string* whyNot = nullptr);
    static bool fisherCdf(const ICoreMatrix& x, const ICoreMatrix& first,
                          const ICoreMatrix& second, const Tail& tail, ICoreMatrix& out,
                          std::string* whyNot = nullptr);
    static bool fisherInv(const ICoreMatrix& p, const ICoreMatrix& first,
                          const ICoreMatrix& second, ICoreMatrix& out,
                          std::string* whyNot = nullptr);

    // ---- the four samplers (T3.3's normrnd, T3.5's trnd/chi2rnd/frnd) ----
    //
    // ⚠ **These four cannot be parity-compared at all, and it is not a
    // tolerance question.** MATLAB's `randn` is a Mersenne Twister stream and
    // this console's is not, so the same seed does not produce the same draw
    // and no band makes it. They cross with `Compare::Skip` (C0.9) and are
    // pinned by regress cases on a DERIVED property instead -- a count, a
    // shape, a sign -- which is the shape `rand`'s own cases already take
    // (session.itest: `numel(rand(2, 3))`, `nnz(floor(rand(5, 5)))`).
    //
    // `rows` and `columns` of 0 ask for MATLAB's parameter-shaped answer: a
    // draw per entry of the broadcast parameters, which for scalars is one.
    static bool normalRandom(const ICoreMatrix& mean, const ICoreMatrix& deviation,
                             const size_t& rows, const size_t& columns, ICoreMatrix& out,
                             std::string* whyNot = nullptr);
    static bool studentRandom(const ICoreMatrix& degrees, const size_t& rows,
                              const size_t& columns, ICoreMatrix& out,
                              std::string* whyNot = nullptr);
    static bool chiSquareRandom(const ICoreMatrix& degrees, const size_t& rows,
                                const size_t& columns, ICoreMatrix& out,
                                std::string* whyNot = nullptr);
    static bool fisherRandom(const ICoreMatrix& first, const ICoreMatrix& second,
                             const size_t& rows, const size_t& columns, ICoreMatrix& out,
                             std::string* whyNot = nullptr);

    // ---- the two inverses base MATLAB has and this console did not --------
    //
    // `betaincinv(p, a, b)` and `gammaincinv(p, a)`, solved by Newton on
    // `betainc`/`gammainc`. They are PUBLIC because three of the six inverses
    // above are one call to them plus a change of variable, and because the
    // gamma and beta distribution families (toolbox row T3.6) will want the
    // same two functions -- one implementation, so a tolerance cannot drift
    // between the rows that share it.
    static double incompleteBetaInverse(const double& p, const double& a, const double& b,
                                        const Tail& tail);
    static double incompleteGammaInverse(const double& p, const double& a,
                                        const Tail& tail = Tail::Lower);

    // ==== mb143 T3.4 / T3.6 begin ========================================
    //
    // ---- the remaining univariate families (toolbox rows T3.4 and T3.6) --
    //
    // Fifteen families, and they are ONE entry point each rather than ninety
    // methods, because ninety methods would be ninety copies of the same four
    // lines. What differs between `unifpdf` and `hygepdf` is a scalar formula
    // and how many parameters come after x; what does NOT differ is MATLAB's
    // `distchck` broadcast, the NaN-rather-than-error convention for a
    // parameter out of range, the trailing `"upper"` on every cdf, and the
    // `[m, n]` size tail on every sampler. Those four live here once, so a
    // family cannot quietly get a different one -- the same argument that made
    // `incompleteBetaInverse` public for this row rather than copied into it.
    //
    // The families the T3.5 header already named as this row's callers are
    // exactly the ones that appear below as one line: `gamcdf(x, a, b)` IS
    // `gammainc(x/b, a)` and `gaminv` IS `gammaincinv(p, a) * b`, `betacdf`
    // and `betainv` are `betainc`/`betaincinv` unchanged.
    enum class Family {
        // T3.4
        Uniform, Exponential, Poisson, Binomial,
        // T3.6
        Gamma, Beta, Lognormal, Weibull, Rayleigh, ExtremeValue,
        GeneralizedExtremeValue, GeneralizedPareto, NegativeBinomial, Geometric,
        Hypergeometric,
    };

    // How many parameters follow x, how many of them MATLAB insists on for a
    // `*pdf`/`*cdf`/`*inv`/`*rnd`, and how many it insists on for the `*stat`
    // pair -- which is NOT always the same number: `gpstat(k, sigma)` takes
    // two of three and `gevstat(k, sigma, mu)` takes all three.
    struct FamilyShape {
        size_t parameters = 0;
        size_t required = 0;
        size_t requiredForMoments = 0;
        // A count-valued family: its quantile is a whole number found by
        // walking the cdf, not a closed form.
        bool discrete = false;
    };
    static FamilyShape shapeOf(const Family& family);

    // `arguments` is x (or p) followed by the family's parameters; a parameter
    // left off takes MATLAB's default where it has one. Every argument is a
    // scalar or the common shape.
    static bool density(const Family& family, const std::vector<ICoreMatrix>& arguments,
                        ICoreMatrix& out, std::string* whyNot = nullptr);
    static bool cumulative(const Family& family, const std::vector<ICoreMatrix>& arguments,
                           const Tail& tail, ICoreMatrix& out, std::string* whyNot = nullptr);
    static bool quantile(const Family& family, const std::vector<ICoreMatrix>& arguments,
                         ICoreMatrix& out, std::string* whyNot = nullptr);
    // MATLAB's [m, v] = <family>stat(...): the mean and the VARIANCE.
    static bool moments(const Family& family, const std::vector<ICoreMatrix>& parameters,
                        ICoreMatrix& outMean, ICoreMatrix& outVariance,
                        std::string* whyNot = nullptr);
    // ⚠ Every sampler here crosses the parity suite with `Compare::Skip` for
    // `normrnd`'s reason -- MATLAB's `rand`/`randn`/`randg` are one Mersenne
    // Twister stream and this console's are not -- so what these reproduce is
    // the DISTRIBUTION and the shape, never the draw (Z11).
    static bool sample(const Family& family, const std::vector<ICoreMatrix>& parameters,
                       const size_t& rows, const size_t& columns, ICoreMatrix& out,
                       std::string* whyNot = nullptr);
    // MATLAB's `<family>fit(x[, alpha])`, and `binofit(x, n[, alpha])` -- the
    // only one with a second DATA argument, which is why `trials` is a
    // parameter here rather than folded into `data`.
    //
    // `estimate` is MATLAB's `parmhat` and `interval` its `parmci`, each in
    // MATLAB's own shape: `expfit` answers a number and a 2 x 1, `lognfit` a
    // 1 x 2 and a 2 x 2, `binofit` a number and a 1 x 2, and `unifit` a 1 x 2
    // whose interval columns are its third and fourth outputs.
    //
    // The four families whose fit is `fminsearch` on the log-likelihood
    // (`beta`, `nbin`, `gev`, `gp`) are NOT here and are refused by name: a
    // simplex that stops at TolX 1e-6 reports the point it stopped at, and
    // that is determined by the PATH rather than by an equation (C8.22). The
    // four that are here are a closed form or a bracketed root, which is not.
    static bool fit(const Family& family, const ICoreMatrix& data, const ICoreMatrix& trials,
                    const double& alpha, ICoreMatrix& estimate, ICoreMatrix& interval,
                    std::string* whyNot = nullptr);
    // ==== mb143 T3.4 / T3.6 end ==========================================

    // ==== mb143 T3.9 begin ===============================================
    //
    // ---- `mle` for a NAMED distribution (toolbox row T3.9) ---------------
    //
    // ⚠ **`mle` is not an estimator.** `mle.m` is a switch over eighteen
    // distribution names that calls the `*fit` functions above -- `binofit`,
    // `unifit`, `expfit`, `poissfit`, `normfit`, `lognfit`, `raylfit`,
    // `gamfit`, `evfit`, `wblfit` -- plus four closed forms of its own, and
    // routes everything else through `fitdist` to a distribution OBJECT. So
    // this row is a dispatch, and the only arithmetic it adds is those four
    // and one correction.
    //
    // ⚠ **The correction is the trap, and it is a real one**:
    // `mle(x, "Distribution", "normal")` is NOT `normfit(x)`. `fitdist`
    // answers the UNBIASED deviation (divided by n - 1) and `mle` multiplies
    // it by `sqrt((n - 1)/n)` to get the maximum-likelihood one, so the two
    // functions disagree on the second parameter by 6.5% at n = 8 and agree
    // on the first exactly. Measured on R2026a: `normfit` 0.86147713674992976,
    // `mle` 0.80583807306430988. **The INTERVAL is not corrected** -- `mle`
    // hands back `normfit`'s, built from the unbiased estimate -- so a reader
    // who "fixes" the interval to match the estimate breaks it. Lognormal
    // does the same thing on the same line of `mle.m`.
    //
    // Three of the names here have no `*pdf` on this console at all, because
    // no row asked for one and MATLAB has no `bernfit`, `unidfit` or `hnfit`
    // either: `mle` is the only way to fit them, which is why they are an
    // enumerator of their own rather than three more `Family` members whose
    // density nothing would answer.
    enum class MleDistribution {
        Normal, Lognormal, Exponential, Poisson, Rayleigh, Gamma, Weibull,
        ExtremeValue, Uniform, Geometric, DiscreteUniform, Bernoulli, Binomial,
        HalfNormal,
    };

    // `trials` is `mle`'s `"ntrials"` (binomial only) and `location` its
    // `"mu"` (half normal only); both are ignored elsewhere, as MATLAB
    // ignores them with a warning. `estimate` and `interval` are MATLAB's
    // `phat` and `pci` in MATLAB's own shapes -- and note that a
    // one-parameter family's `pci` is a 2 x 1 here where `binofit`'s is a
    // 1 x 2, because `mle.m` transposes it.
    static bool maximumLikelihood(const MleDistribution& distribution, const ICoreMatrix& data,
                                  const double& alpha, const double& trials,
                                  const double& location, ICoreMatrix& estimate,
                                  ICoreMatrix& interval, std::string* whyNot = nullptr);
    // ==== mb143 T3.9 end =================================================

};
};

ICoreEmpiricalDistributions.h#

src/ICoreBlocks/ICoreMath/Statistics/ICoreEmpiricalDistributions.h

ICoreEmpiricalDistributions#

ICoreEmpiricalDistributions.h:47 · class · nested KernelRequest · 4 declaration(s)

The two empirical distribution estimates of toolbox row T3.24 -- MATLAB's ecdf and ksdensity.

class ICoreEmpiricalDistributions {
public:
    // MATLAB's three `ecdf` functions. `"chf"` and `"cumulative hazard"` are
    // spellings of the third.
    enum class Empirical { Cdf, Survivor, CumulativeHazard };

    // The function word, MATLAB's own list including its abbreviations.
    static bool empiricalOf(const std::string& word, Empirical& which);

    // `[f, x, flo, fup] = ecdf(y, ...)`. `censoring` and `frequency` may be
    // empty matrices, which is "none given". `alpha` is the 0.05 of MATLAB's
    // default and only matters when `wantBounds` is true; the bounds are
    // Greenwood's, with a NaN in the first row because the starting point is
    // not an estimate.
    //
    // Left-censored, interval-censored and doubly censored data are refused:
    // each is a different estimator in MATLAB (Ware-Demets, Turnbull, and an
    // EM iteration), not an option of this one.
    static bool empiricalCdf(const ICoreMatrix& y, const ICoreMatrix& censoring,
                             const ICoreMatrix& frequency, const Empirical& which,
                             const double& alpha, const bool& wantBounds,
                             ICoreMatrix& f, ICoreMatrix& x,
                             ICoreMatrix& lower, ICoreMatrix& upper,
                             std::string* whyNot = nullptr);

    // MATLAB's four built-in smoothing kernels. Each is scaled to unit
    // variance, which is what makes the bandwidth mean the same thing across
    // them -- and why each has its own cutoff.
    enum class Kernel { Normal, Box, Triangle, Epanechnikov };

    // What the estimate answers. `Icdf` is deliberately absent (see above).
    enum class Density { Pdf, Cdf, Survivor, CumulativeHazard };

    static bool kernelOf(const std::string& word, Kernel& kernel);
    static bool densityOf(const std::string& word, Density& which);

    // Everything a `ksdensity` call carries besides its data. `bandwidth` at
    // or below zero means "estimate it", which is MATLAB's empty default;
    // `lower`/`upper` are the support, `-Inf`/`Inf` for the unbounded default.
    struct KernelRequest {
        Kernel kernel = Kernel::Normal;
        Density function = Density::Pdf;
        double bandwidth = 0.0;
        double lower = 0.0;
        double upper = 0.0;
        size_t points = 100;
        bool pointsGiven = false;
        ICoreMatrix at;
    };

    // A request with MATLAB's defaults already in it: normal kernel, pdf,
    // estimated bandwidth, unbounded support, 100 points.
    static KernelRequest defaultRequest();

    // `[f, xi, u] = ksdensity(y, ...)`. `at` comes back holding the points the
    // estimate was evaluated at -- the caller's own when it gave some, and the
    // default grid otherwise -- and `bandwidth` the one that was used, which
    // is the third output MATLAB answers.
    static bool kernelDensity(const ICoreMatrix& y, const KernelRequest& request,
                              ICoreMatrix& f, ICoreMatrix& at, double& bandwidth,
                              std::string* whyNot = nullptr);

};
};

ICoreGroupComparisons.h#

src/ICoreBlocks/ICoreMath/Statistics/ICoreGroupComparisons.h

ICoreGroupComparisons#

ICoreGroupComparisons.h:39 · class · 0 declaration(s)

The nonparametric tests and the analysis of variance of toolbox row T3.12 -- signtest, signrank, ranksum, anova1, anova2, kruskalwallis and friedman.

class ICoreGroupComparisons {
public:
    using Tail = ICoreHypothesisTests::Tail;

    // `[p, h] = signtest(x[, y][, "Alpha", a][, "Tail", t])` -- are the
    // differences centred on zero? Only their SIGNS are used, so it is the
    // weakest of the three and the one that survives any monotone rescaling.
    //
    // Pairs that differ by exactly zero are DROPPED, and the binomial the
    // exact arm uses is over what is left.
    static bool signTest(const ICoreMatrix& x, const ICoreMatrix& y, const bool& paired,
                         const double& alpha, const Tail& tail, double& probability,
                         double& decision, std::string* whyNot = nullptr);

    // `[p, h] = signrank(x[, y][, "Alpha", a][, "Tail", t])` -- the same
    // question using the RANKS of the differences as well as their signs.
    //
    // ⚠ Its zero test is not `== 0`. `signrank.m` drops a pair whose
    // difference is within `eps(x) + eps(y)` of zero, which is a wider net
    // than equality and is what stops two numbers that differ in their last
    // bit from contributing a rank.
    static bool signedRankTest(const ICoreMatrix& x, const ICoreMatrix& y, const bool& paired,
                               const double& alpha, const Tail& tail, double& probability,
                               double& decision, std::string* whyNot = nullptr);

    // `[p, h] = ranksum(x, y[, "Alpha", a][, "Tail", t])` -- the two-sample
    // rank sum (Mann-Whitney U), on samples that need not be the same length.
    //
    // The statistic is the rank sum of the SMALLER sample, so which of the two
    // arguments that is decides what a one-sided test means; MATLAB tracks it
    // and flips the tail to match, and so does this.
    static bool rankSumTest(const ICoreMatrix& x, const ICoreMatrix& y, const double& alpha,
                            const Tail& tail, double& probability, double& decision,
                            std::string* whyNot = nullptr);

    // `p = anova1(y[, group])` and `p = kruskalwallis(y[, group])` -- one-way,
    // over the COLUMNS of a matrix or over a vector split by a grouping
    // vector. `kruskalwallis` is `anova1` on the tied ranks with a
    // chi-square in place of the F, which is how `kruskalwallis.m` is written:
    // one line, forwarding to `anova1`.
    static bool oneWay(const ICoreMatrix& x, const ICoreMatrix& group, const bool& grouped,
                       const bool& byRanks, double& probability, double& statistic,
                       double& firstDegrees, double& secondDegrees,
                       std::string* whyNot = nullptr);

    // `p = anova2(y[, reps])` -- two-way, the rows being one factor and the
    // columns the other. Answers MATLAB's row of p-values: the column effect,
    // the row effect, and with more than one replicate the interaction too.
    static bool twoWay(const ICoreMatrix& x, const size_t& replicates, ICoreMatrix& probabilities,
                       std::string* whyNot = nullptr);

    // `p = friedman(y[, reps])` -- the rank analogue of `anova2`, ranking
    // WITHIN each block before the analysis and reading a chi-square off the
    // column sum of squares.
    static bool friedmanTest(const ICoreMatrix& x, const size_t& replicates, double& probability,
                             double& statistic, std::string* whyNot = nullptr);

};
};

ICoreHypothesisTests.h#

src/ICoreBlocks/ICoreMath/Statistics/ICoreHypothesisTests.h

ICoreHypothesisTests#

ICoreHypothesisTests.h:42 · class · 2 declaration(s)

The variance and goodness-of-fit tests of toolbox row T3.11 -- vartest, vartest2, chi2gof, kstest, kstest2, lillietest, jbtest, adtest and runstest.

class ICoreHypothesisTests {
public:
    // ⚠ THE TAIL WORDS ARE NOT THE SAME ACROSS THIS ROW, and neither is the
    // CLASS of the decision. `vartest`, `vartest2` and `runstest` take
    // `"both"`, `"left"` and `"right"` and answer a DOUBLE `h`; `kstest` and
    // `kstest2` take `"unequal"`, `"smaller"` and `"larger"` for the same
    // three tails and answer a LOGICAL one, as does `adtest`. Both
    // inconsistencies are MATLAB's and both are carried: a console that took
    // `"both"` for `kstest` would accept a script MATLAB refuses, and one
    // that answered a double there would report a different `class()` for the
    // same call. `tailOf` below is the first vocabulary; the second lives at
    // the call site, because it belongs to two names rather than to the row.
    enum class Tail { Left, Both, Right };
    static bool tailOf(const std::string& word, Tail& tail);

    // The three distribution families `lillietest` and `adtest` share. MATLAB
    // reaches the lognormal and the Weibull by taking logs and testing the
    // normal and the extreme value, which is what this does.
    enum class Family { Normal, Exponential, ExtremeValue, Lognormal, Weibull };
    static bool familyOf(const std::string& word, Family& family);

    // `[h, p, ci] = vartest(x, v[, "Alpha", a][, "Tail", t])` -- is the
    // variance of x equal to v? The statistic is `sum((x - mean(x))^2) / v`
    // against a chi-square with n-1 degrees of freedom.
    static bool varianceTest(const ICoreMatrix& x, const double& variance, const double& alpha,
                             const Tail& tail, double& decision, double& probability,
                             ICoreMatrix& interval, std::string* whyNot = nullptr);

    // `[h, p, ci] = vartest2(x, y[, "Alpha", a][, "Tail", t])` -- do two
    // samples have the same variance? The ratio of the two variances against
    // an F.
    static bool varianceTest2(const ICoreMatrix& x, const ICoreMatrix& y, const double& alpha,
                              const Tail& tail, double& decision, double& probability,
                              ICoreMatrix& interval, std::string* whyNot = nullptr);

    // `[h, p, stat, df] = chi2gof(x[, "NBins", n][, "Emin", m][, "NParams", k]
    //                             [, "Alpha", a])`.
    //
    // ⚠ THE POOLING IS THE TEST. MATLAB bins into ten equal-width bins by
    // default, then POOLS neighbouring bins from whichever end has the
    // smaller expected count until every remaining bin expects at least
    // `emin` (5 by default) -- so the degrees of freedom depend on the data,
    // not only on the bin count. A `chi2gof` that skips the pooling answers a
    // different number with the same formula.
    //
    // The default null is a normal fitted to the data, so two parameters are
    // estimated and `df = bins - 1 - 2`.
    static bool chiSquareGoodnessOfFit(const ICoreMatrix& x, const size_t& bins,
                                       const double& minimumExpected, const double& parameters,
                                       const double& alpha, double& decision, double& probability,
                                       double& statistic, double& degrees,
                                       std::string* whyNot = nullptr);

    // `[h, p, ksstat, cv] = kstest(x[, "Alpha", a][, "Tail", t])` against the
    // STANDARD normal -- which is MATLAB's default null and catches people
    // out: `kstest` does not standardize x for you, and `kstest(x)` on data
    // with any other mean or spread rejects for that reason alone.
    //
    // The p-value is exact rather than asymptotic below a threshold MATLAB
    // sets on `n * D^2`: Marsaglia, Tsang and Wang's method, which raises a
    // (2k-1)-square Toeplitz matrix to the n-th power. Above the threshold it
    // is a three-term asymptotic form. Both arms are here, because the
    // threshold is part of the answer.
    //
    // ⚠ The CRITICAL VALUE has three arms of its own, chosen on the ONE-SIDED
    // level: outside [0.005, 0.10] MATLAB answers NaN rather than
    // extrapolating; at or under twenty observations it reads an exact table
    // with a NOT-A-KNOT SPLINE -- a different interpolant from the `pchip`
    // the three tabulated tests use, on the same kind of table -- and above
    // twenty it is a three-term fit capped at `1 - alpha`.
    static bool kolmogorovSmirnov(const ICoreMatrix& x, const double& alpha, const Tail& tail,
                                  double& decision, double& probability, double& statistic,
                                  double& critical, std::string* whyNot = nullptr);

    // `[h, p, ks2stat] = kstest2(x, y[, "Alpha", a][, "Tail", t])` -- the
    // two-sample test, whose p-value is the asymptotic Kolmogorov series with
    // Stephens' small-sample correction on lambda.
    static bool kolmogorovSmirnov2(const ICoreMatrix& x, const ICoreMatrix& y, const double& alpha,
                                   const Tail& tail, double& decision, double& probability,
                                   double& statistic, std::string* whyNot = nullptr);

    // `[h, p, kstat, critval] = lillietest(x[, "Distribution", d][, "Alpha", a])`
    // -- the Kolmogorov-Smirnov statistic against a distribution whose
    // parameters were ESTIMATED FROM THE SAME DATA, which is why its null
    // distribution is tabulated rather than computed.
    static bool lilliefors(const ICoreMatrix& x, const Family& family, const double& alpha,
                           double& decision, double& probability, double& statistic,
                           double& critical, std::string* whyNot = nullptr);

    // `[h, p, adstat, cv] = adtest(x[, "Distribution", d][, "Alpha", a])` --
    // the Anderson-Darling statistic, which weights the tails where the
    // Kolmogorov-Smirnov statistic weights the middle. Same reason for the
    // table, and its table is interpolated in LOG alpha rather than in alpha.
    static bool andersonDarling(const ICoreMatrix& x, const Family& family, const double& alpha,
                                double& decision, double& probability, double& statistic,
                                double& critical, std::string* whyNot = nullptr);

    // `[h, p, jbstat, critval] = jbtest(x[, alpha])` -- skewness and kurtosis
    // together, against a table indexed by sample size AND by alpha, which
    // MATLAB interpolates in `1/n` first and then in alpha.
    static bool jarqueBera(const ICoreMatrix& x, const double& alpha, double& decision,
                           double& probability, double& statistic, double& critical,
                           std::string* whyNot = nullptr);

    // `[h, p] = runstest(x[, v][, "Alpha", a][, "Tail", t])` -- are the values
    // above and below `v` in random order?
    //
    // ⚠ THE DEFAULT `v` IS THE MEAN, not the median. `runstest.m` says
    // `v = mean(x)` and a reader who assumes the median gets a different
    // sequence of ones and zeros and a different answer. Values exactly equal
    // to `v` are DROPPED before anything is counted.
    //
    // The p-value is exact -- a closed form in binomial coefficients over the
    // number of runs -- and not the normal approximation, which MATLAB keeps
    // only for the up-and-down variant this console refuses.
    static bool runsTest(const ICoreMatrix& x, const double& threshold, const double& alpha,
                         const Tail& tail, double& decision, double& probability,
                         double& runs, double& normalScore, std::string* whyNot = nullptr);

    // ---- the LOCATION tests (toolbox row T3.10) --------------------------
    //
    // `ttest`, `ttest2` and `ztest`: three tests of a mean, and every number
    // they answer is a composition over cdfs T3.5 and T3.3 already landed --
    // nothing here is estimated or tabulated. What the row is really about is
    // three conventions that no signature states:
    //
    //   ⚠ `ttest(x, y)` IS PAIRED. Two samples handed to `ttest` are the
    //     SAME subjects measured twice, and the test runs on x - y with n - 1
    //     degrees of freedom; `ttest2(x, y)` is the unpaired test with
    //     n1 + n2 - 2. The two answer different p-values for the same pair of
    //     vectors and neither call says which it is.
    //
    //   ⚠ A ONE-SIDED interval is HALF-INFINITE. MATLAB's right-tailed ci is
    //     [lower, Inf] and its left-tailed one [-Inf, upper] -- not the
    //     two-sided interval with one end reported.
    //
    //   ⚠ 'Vartype', 'unequal' changes the DEGREES OF FREEDOM and not just
    //     the standard error: Welch-Satterthwaite gives a fractional df
    //     (14.21215207001414 on this row's own fixture), and a test that
    //     kept n1 + n2 - 2 there answers a p-value that is wrong in the
    //     third digit and plausible in every other way.
    //
    // `stats` -- MATLAB's fourth output, a struct of tstat/df/sd -- is a kind
    // this console does not have (T0.6) and is refused by name at the call
    // site rather than flattened into a vector whose fields nobody can tell
    // apart.
    //
    // `paired` selects ttest's two-sample reading; `pooled` selects ttest2's
    // (false is Welch). `deviation` above zero makes it `ztest` -- the
    // normal test with a KNOWN standard deviation, where the interval is
    // MATLAB's z-interval and the p-value the normal tail.
    static bool locationTest(const ICoreMatrix& x, const ICoreMatrix& y, const double& mean,
                             const double& alpha, const Tail& tail, const bool& paired,
                             const bool& pooled, const double& deviation, double& decision,
                             double& probability, ICoreMatrix& interval, double& statistic,
                             double& degreesOfFreedom, double& standardDeviation,
                             std::string* whyNot = nullptr);
};
};

ICoreLinearRegression.h#

src/ICoreBlocks/ICoreMath/Statistics/ICoreLinearRegression.h

ICoreLinearRegression#

ICoreLinearRegression.h:50 · class · 0 declaration(s)

regress -- toolbox row T3.13, and all five of its outputs.

class ICoreLinearRegression {
public:
    // `[b, bint, r, rint, stats] = regress(y, X[, alpha])`. Rows where `y` or
    // any column of `X` is missing are dropped before anything is computed and
    // NaN is put back into `r` and `rint` afterwards, as regress.m does.
    static bool regress(const ICoreMatrix& y, const ICoreMatrix& x, const double& alpha,
                        ICoreMatrix& coefficients, ICoreMatrix& coefficientInterval,
                        ICoreMatrix& residuals, ICoreMatrix& residualInterval,
                        ICoreMatrix& statistics, std::string* whyNot = nullptr);

};
};

ICoreMultivariateAnalysis.h#

src/ICoreBlocks/ICoreMath/Statistics/ICoreMultivariateAnalysis.h

ICoreMultivariateAnalysis#

ICoreMultivariateAnalysis.h:34 · class · 1 declaration(s)

The multivariate block of the Statistics and Machine Learning Toolbox that is NOT principal components -- toolbox row T3.26: canoncorr, cmdscale, procrustes and rotatefactors.

class ICoreMultivariateAnalysis {
public:

    // `[A, B, r, U, V] = canoncorr(X, Y)` -- the canonical correlations of two
    // sets of columns measured on the same n rows. Both are centred first;
    // `A` and `B` are the weights that carry them into the canonical
    // variables `U = Xc*A` and `V = Yc*B`, and `r(k)` is the correlation
    // between column k of each.
    //
    // A rank-deficient X or Y is NOT refused: `canoncorr.m` warns, drops the
    // deficient columns from the factorization and puts explicit zero rows
    // back into A and B at the pivot positions, and this does the same, so a
    // constant column answers a zero weight rather than an error.
    static bool canonicalCorrelation(const ICoreMatrix& x, const ICoreMatrix& y,
                                     ICoreMatrix& a, ICoreMatrix& b, ICoreMatrix& r,
                                     ICoreMatrix& u, ICoreMatrix& v,
                                     std::string* whyNot = nullptr);

    // `[Y, e] = cmdscale(D)` and `cmdscale(D, p)` -- classical (metric)
    // multidimensional scaling: the point configuration whose interpoint
    // distances reproduce D.
    //
    // `d` may be a full square dissimilarity matrix, a SIMILARITY matrix (unit
    // diagonal and every entry below 1, which cmdscale.m turns into
    // `sqrt(1 - D)`), or the `pdist` row of the lower triangle, which it
    // squareforms. `dimensions` is 0 for "all that survive"; a positive p asks
    // for exactly p columns.
    //
    // ⚠ `e` IS THE WHOLE EIGENVALUE SPECTRUM AND `Y` IS NOT ALL OF IT: the
    // columns kept are those with `e > max(abs(e)) * eps^(3/4)`, so a set of n
    // points in 3 dimensions answers an n x 3 Y beside an n x n `e` whose tail
    // is rounding noise. Reading `size(Y, 2)` as `numel(e)` is the trap.
    static bool classicalScaling(const ICoreMatrix& d, const size_t& dimensions,
                                 ICoreMatrix& configuration, ICoreMatrix& eigenvalues,
                                 std::string* whyNot = nullptr);

    // `procrustes.m`'s 'Reflection': 'best' lets the data decide, and the two
    // explicit values force one in or out.
    enum class Reflection { Best, Forced, Forbidden };

    // `[d, Z, transform] = procrustes(X, Y)` -- the rotation, scaling and
    // translation of Y that lands it closest to X, and the standardized
    // residual `d` that is left.
    //
    // `transform` is a struct in MATLAB and this console has no struct kind
    // (T0.6), so the CONSOLE's `procrustes` stops at `[d, Z]` and refuses the
    // third output by name -- inventing a three-output spelling MATLAB does
    // not have would be the silent divergence C0.1 rejects. This function
    // still answers the three fields, because the parity helper and the
    // console's own tests need them: `rotation` is `transform.T`, `scale` is
    // `transform.b` and `translation` is the one ROW of `transform.c` (MATLAB
    // repeats that row n times).
    //
    // ⚠ Y MAY HAVE FEWER COLUMNS THAN X and is zero-padded up to X's
    // dimension, which is how a 2-D shape is fitted into a 3-D one; `T` then
    // comes back with Y's own number of ROWS, not X's.
    static bool procrustesAnalysis(const ICoreMatrix& x, const ICoreMatrix& y,
                                   const bool& scaling, const Reflection& reflection,
                                   double& dissimilarity, ICoreMatrix& transformed,
                                   ICoreMatrix& rotation, double& scale,
                                   ICoreMatrix& translation, std::string* whyNot = nullptr);

    // `rotatefactors.m`'s methods, less the two this row does not land.
    // Orthomax is the family: varimax is gamma = 1, quartimax gamma = 0,
    // equamax gamma = m/2 and parsimax gamma = d*(m-1)/(d+m-2), all four
    // reaching the same iteration through a different coefficient.
    enum class Rotation { Orthomax, Varimax, Quartimax, Equamax, Parsimax,
                          Procrustes, Promax };

    // `[B, T] = rotatefactors(A, ...)`. `coefficient` is 'Coeff' (used only by
    // Orthomax), `power` is 'Power' (Promax, default 4), `normalize` is
    // 'Normalize' (default on -- Kaiser's row normalisation), `tolerance` is
    // 'Reltol' (default sqrt(eps)) and `iterations` is 'Maxit' (default 250).
    // `target` and `oblique` are the Procrustes method's 'Target' and 'Type';
    // `target` is ignored by every other method.
    //
    // ⚠ ORTHOMAX CAN ASK FOR A RANDOM START AND THIS REFUSES INSTEAD. When the
    // identity rotation already satisfies the convergence test at the first
    // step -- a loading matrix that is ALREADY rotated -- `rotatefactors.m`
    // restarts from `qr(randn(m, m))`, so its answer is a draw from MATLAB's
    // own Mersenne Twister and reproducible on no other stream (Z11). That
    // branch refuses by name; every other input takes the deterministic path
    // from `T = eye(m)` and agrees with MATLAB entry for entry.
    static bool rotateFactors(const ICoreMatrix& loadings, const Rotation& method,
                              const double& coefficient, const double& power,
                              const bool& normalize, const double& tolerance,
                              const size_t& iterations, const ICoreMatrix& target,
                              const bool& oblique, ICoreMatrix& rotated,
                              ICoreMatrix& rotation, std::string* whyNot = nullptr);

    // The method word, as `rotatefactors(A, 'Method', word)` spells it.
    // 'equimax' and 'equamax' are the same rotation, which rotatefactors.m
    // accepts under both spellings and under the prefix 'equ'.
    static bool rotationOf(const std::string& word, Rotation& out);
};
};

ICoreMultivariateDensities.h#

src/ICoreBlocks/ICoreMath/Statistics/ICoreMultivariateDensities.h

ICoreMultivariateDensities#

ICoreMultivariateDensities.h:36 · class · 0 declaration(s)

The multivariate normal and t densities of toolbox row T3.8 -- mvnpdf, mvncdf, mvtpdf and the four samplers that go with them.

class ICoreMultivariateDensities {
public:
    // `mvnpdf(X[, Mu[, Sigma]])` -- one density per ROW of X, as a column.
    //
    // `mu` may be empty (the origin), a scalar, one row, or a row per row of
    // X. `sigma` may be empty (the identity), a d x d covariance, or a 1 x d
    // ROW, which MATLAB reads as the DIAGONAL of a covariance and not as a
    // 1 x d covariance -- the shape alone decides, so a one-dimensional call
    // with two points cannot spell its variance as a row.
    static bool normalDensity(const ICoreMatrix& x, const ICoreMatrix& mu,
                              const ICoreMatrix& sigma, const bool& haveMu,
                              const bool& haveSigma, ICoreMatrix& out,
                              std::string* whyNot = nullptr);

    // `mvtpdf(X, C, df)` -- the multivariate Student t density. `C` is a
    // correlation matrix; see the header note on what MATLAB does when it is
    // handed a covariance instead.
    static bool studentDensity(const ICoreMatrix& x, const ICoreMatrix& correlation,
                               const ICoreMatrix& degrees, ICoreMatrix& out,
                               std::string* whyNot = nullptr);

    // `mvncdf(XU[, mu, Sigma])` and `mvncdf(XL, XU, mu, Sigma)` -- the
    // probability in the box, one per row, as a column.
    //
    // `lower` empty is MATLAB's three-argument form, whose lower limit is
    // -Inf in every coordinate. The refusal above three columns is this row's
    // and names Z11.
    static bool normalCumulative(const ICoreMatrix& lower, const ICoreMatrix& upper,
                                 const ICoreMatrix& mu, const ICoreMatrix& sigma,
                                 const bool& haveLower, const bool& haveMu,
                                 const bool& haveSigma, ICoreMatrix& out,
                                 std::string* whyNot = nullptr);

    // The bivariate standardized cdf itself, exposed because two rows want it
    // and one implementation is the point: `internal.stats.bvncdf(b, rho)`,
    // P(Z1 <= b1, Z2 <= b2) for the standard bivariate normal of correlation
    // rho. `b` entries may be -Inf.
    static double bivariateNormalCdf(const double& first, const double& second,
                                     const double& rho);

    // ---- the four samplers ------------------------------------------------
    //
    // ⚠ None of these four can be parity-compared, for the reason T3.3's
    // `normrnd` states: MATLAB's `randn` is a Mersenne Twister stream and this
    // console's is not, so the same seed is not the same draw and no tolerance
    // band makes it one. They cross with `Compare::Skip` (C0.9, board rule
    // Z11) and are pinned by regress cases on a derived property -- a shape, a
    // count, a symmetry -- exactly as `rand`'s own cases are.

    // `mvnrnd(mu, Sigma[, n])` -- n rows, each `mu + z * chol(Sigma)`.
    static bool normalRandom(const ICoreMatrix& mu, const ICoreMatrix& sigma,
                             const size_t& rows, ICoreMatrix& out,
                             std::string* whyNot = nullptr);

    // `mvtrnd(C, df[, n])` -- n rows of the multivariate t, each a normal row
    // divided by the square root of its own scaled chi-square draw.
    static bool studentRandom(const ICoreMatrix& correlation, const double& degrees,
                              const size_t& rows, ICoreMatrix& out,
                              std::string* whyNot = nullptr);

    // `wishrnd(Sigma, df)` and `iwishrnd(Sigma, df)` -- one d x d draw.
    // Both go through Smith and Hocking's Bartlett decomposition, which is
    // what `wishrnd.m` does: a lower triangle of standard normals under a
    // diagonal of chi roots, so a draw costs d(d+1)/2 numbers rather than a
    // df x d sample.
    static bool wishartRandom(const ICoreMatrix& sigma, const double& degrees,
                              const bool& inverse, ICoreMatrix& out,
                              std::string* whyNot = nullptr);

};
};

ICorePenalizedRegression.h#

src/ICoreBlocks/ICoreMath/Statistics/ICorePenalizedRegression.h

ICorePenalizedRegression#

ICorePenalizedRegression.h:48 · class · nested LassoOptions · 0 declaration(s)

The penalized and latent-variable regressions of toolbox row T3.15: lasso, ridge and plsregress.

class ICorePenalizedRegression {
public:
    // `lasso`'s name/value options, with `lasso.m`'s own defaults. The four
    // that are NOT here are refused at the console rather than defaulted:
    // `'CV'` and `'MCReps'` (random folds -- Z11 -- and a `cvpartition` is an
    // object, Z3), `'Options'` (a parallel-pool struct) and
    // `'PredictorNames'` (a cellstr that only ever reaches the refused
    // `FitInfo`).
    struct LassoOptions {
        // The elastic-net mixing, `0 < alpha <= 1`. 1 is the lasso; below it
        // the ridge half of the penalty enters through `shrinkFactor`.
        double alpha = 1.0;
        // The default sequence: `numLambda` values geometric from `lambdaMax`
        // down to `lambdaMax * lambdaRatio`. ⚠ `lambdaRatio == 0` is not "no
        // ratio" -- it means "use 1e-4 and then make the LAST value exactly
        // zero", which is the unpenalized fit and is how `lasso.m` spells it.
        size_t numLambda = 100;
        double lambdaRatio = 1.0e-4;
        // A caller-supplied sequence, sorted DESCENDING before use. When this
        // is non-empty the overfit break below is disabled, because a caller
        // who named the penalties gets all of them.
        std::vector<double> lambda;
        // `'DFmax'`: stop once a fit has more than this many nonzero
        // coefficients. 0 means "no limit" (`lasso.m` clamps it to `P`).
        size_t degreesOfFreedomMax = 0;
        bool standardize = true;
        double relativeTolerance = 1.0e-4;
        size_t maximumIterations = 100000;
        bool intercept = true;
        // `'Weights'`, one per observation; normalised to sum to 1 as
        // `standardizeW` does. Empty is unweighted, which is NOT the same
        // arithmetic as equal weights -- the unweighted path divides by `N`
        // where the weighted one divides by 1.
        std::vector<double> weights;
        // `'UseCovariance'`: -1 is MATLAB's `'auto'` (false when `N <= D`,
        // otherwise true unless the Gram matrix would exceed `cacheSize`
        // megabytes). The two paths are the same algebra reached two ways --
        // one keeps the residual, the other the Gram matrix -- so they differ
        // in the last digits and BOTH sides must take the same branch.
        int useCovariance = -1;
        double cacheSize = 1.0e3;
    };

    // `B = lasso(X, y, ...)`. `coefficients` is `P x nLambda` on the ORIGINAL
    // scale of `X`, its columns in ASCENDING lambda order -- `lasso.m` fits
    // from `lambdaMax` downward and reverses everything at the end, so column
    // 1 is the least penalized fit.
    //
    // The three other outputs are the numeric fields of the `FitInfo` struct
    // this console has no kind for (T0.6): `lambdaUsed` (1 x nLambda),
    // `intercepts` (1 x nLambda) and `meanSquaredError` (1 x nLambda). They
    // are filled here so that the row lands whole and the console can answer
    // them the day a record kind exists; nothing but the coefficients crosses
    // the bridge today.
    //
    // ⚠ The sequence can come back SHORTER than `numLambda`: `lasso.m` stops
    // early when a fit exceeds `DFmax`, and -- only for a sequence it built
    // itself -- when the mean squared error falls below `1e-3` of the null
    // model's, which it reads as overfitting.
    static bool lasso(const ICoreMatrix& x, const ICoreMatrix& y, const LassoOptions& options,
                      ICoreMatrix& coefficients, ICoreMatrix& lambdaUsed,
                      ICoreMatrix& intercepts, ICoreMatrix& meanSquaredError,
                      std::string* whyNot = nullptr);

    // `b = ridge(y, X, k[, scaled])`. `k` may hold several penalties, one per
    // COLUMN of the answer. `restoreScale` is MATLAB's `scaled == 0`: see the
    // warning at the top of this file -- the default is the scaled fit.
    //
    // MATLAB solves it as one tall least squares, `[Z; sqrt(k) I] \ [y; 0]`,
    // rather than by forming `(Z'Z + kI)`, and so does this: the two are the
    // same matrix in exact arithmetic and the augmented one is the better
    // conditioned of the two by a square root.
    static bool ridge(const ICoreMatrix& y, const ICoreMatrix& x, const ICoreMatrix& penalties,
                      const bool& restoreScale, ICoreMatrix& coefficients,
                      std::string* whyNot = nullptr);

    // `[XL, YL, XS, YS, beta, pctVar, MSE] = plsregress(X, Y[, ncomp])`,
    // SIMPLS exactly as `plsregress.m`'s local `simpls` writes it, including
    // the TWICE-repeated Gram-Schmidt of the deflation basis and of the Y
    // scores -- the repetition is not belt and braces, it is what keeps the
    // basis orthogonal once the covariance has been deflated a few times.
    //
    // `components` of 0 means `min(n - 1, dx)`, MATLAB's default. `beta` is
    // `(dx + 1) x dy` with the intercept row when `intercept` is true and
    // `dx x dy` without it. The eighth output `stats` is a struct and is
    // refused by name (T0.6).
    static bool partialLeastSquares(const ICoreMatrix& x, const ICoreMatrix& y,
                                    const size_t& components, const bool& intercept,
                                    ICoreMatrix& xLoadings, ICoreMatrix& yLoadings,
                                    ICoreMatrix& xScores, ICoreMatrix& yScores,
                                    ICoreMatrix& beta, ICoreMatrix& percentVariance,
                                    ICoreMatrix& meanSquaredError, std::string* whyNot = nullptr);

};
};

ICorePrincipalComponents.h#

src/ICoreBlocks/ICoreMath/Statistics/ICorePrincipalComponents.h

ICorePrincipalComponents#

ICorePrincipalComponents.h:41 · class · 0 declaration(s)

pca, pcacov and pcares -- toolbox row T3.17.

class ICorePrincipalComponents {
public:
    // `[coeff, score, latent, tsquared, explained, mu] = pca(X)`. `centred`
    // is MATLAB's 'Centered' (default true); `components` is 'NumComponents'
    // (0 for all), and dropping components changes only how many COLUMNS of
    // coeff and score come back -- never latent or explained.
    static bool principalComponents(const ICoreMatrix& x, const bool& centred,
                                    const size_t& components, ICoreMatrix& coefficients,
                                    ICoreMatrix& scores, ICoreMatrix& latent,
                                    ICoreMatrix& tSquared, ICoreMatrix& explained,
                                    ICoreMatrix& means, std::string* whyNot = nullptr);

    // `[coeff, latent, explained] = pcacov(C)` -- the same decomposition of a
    // COVARIANCE matrix instead of the data, through its SVD, with the same
    // sign convention.
    static bool principalComponentsOfCovariance(const ICoreMatrix& c, ICoreMatrix& coefficients,
                                                ICoreMatrix& latent, ICoreMatrix& explained,
                                                std::string* whyNot = nullptr);

    // `[residuals, reconstructed] = pcares(X, ndim)` -- what the first `ndim`
    // components do NOT explain. `ndim` above the usable rank is clamped to
    // `min(n-1, p)`, as pcares.m clamps it, rather than refused.
    static bool principalComponentResiduals(const ICoreMatrix& x, const size_t& dimensions,
                                            ICoreMatrix& residuals, ICoreMatrix& reconstructed,
                                            std::string* whyNot = nullptr);

};
};

ICoreRegressionFits.h#

src/ICoreBlocks/ICoreMath/Statistics/ICoreRegressionFits.h

ICoreRegressionFits#

ICoreRegressionFits.h:34 · class · 1 declaration(s)

The regression fits of toolbox row T3.14: robustfit, glmfit, glmval and nlparci.

class ICoreRegressionFits {
public:
    // `robustfit`'s nine weight functions, with the tuning constants
    // `statrobustwfun.m` pairs them with. The constant is not a default in the
    // ordinary sense -- each is the value that makes that weight function 95%
    // as efficient as least squares on normal data, so changing one changes
    // what the estimator IS.
    enum class WeightFunction {
        Andrews, Bisquare, Cauchy, Fair, Huber, Logistic, Ols, Talwar, Welsch,
    };
    static double defaultTuning(const WeightFunction& weight);

    // MATLAB's five GLM distributions and the eight links. `Canonical` means
    // "whichever link this distribution's row of `getGLMVariance.m` names",
    // which is identity for the normal, logit for the binomial, log for the
    // Poisson, reciprocal for the gamma and the -2 power for the inverse
    // Gaussian -- so the canonical link is a property of the DISTRIBUTION and
    // not a link of its own.
    enum class GlmDistribution { Normal, Binomial, Poisson, Gamma, InverseGaussian };
    enum class Link {
        Canonical, Identity, Log, Logit, Probit, ComplementaryLogLog, LogLog, Reciprocal, Power,
    };

    // `b = robustfit(X, y[, wfun, tune, const])`. A constant column is ADDED
    // unless `addConstant` is false -- the opposite of `regress`, which never
    // adds one, and the trap the two functions share a page for.
    static bool robustFit(const ICoreMatrix& x, const ICoreMatrix& y,
                          const WeightFunction& weight, const double& tune,
                          const bool& addConstant, ICoreMatrix& coefficients,
                          std::string* whyNot = nullptr);

    // `b = glmfit(X, y, distr[, "link", L])`. `trials` is the binomial's N; it
    // is ignored for every other distribution, as MATLAB ignores it with a
    // warning. `linkPower` is the exponent when `link` is `Power`.
    static bool generalizedLinearFit(const ICoreMatrix& x, const ICoreMatrix& y,
                                     const GlmDistribution& distribution, const Link& link,
                                     const double& linkPower, const ICoreMatrix& trials,
                                     const bool& addConstant, ICoreMatrix& coefficients,
                                     std::string* whyNot = nullptr);

    // `yhat = glmval(b, X, link[, "size", N])` -- the inverse link of `X*b`,
    // times N for a binomial. No fitting happens here; this is the forward
    // half on its own, which is why it takes a link name rather than a
    // distribution.
    static bool generalizedLinearValue(const ICoreMatrix& coefficients, const ICoreMatrix& x,
                                       const Link& link, const double& linkPower,
                                       const ICoreMatrix& trials, const bool& addConstant,
                                       ICoreMatrix& fitted, std::string* whyNot = nullptr);

    // The model `nlinfit` fits, as the evaluator hands it over: the
    // coefficient vector and the predictor matrix in, the fitted values out,
    // and a reason when the handle cannot be called. A console function
    // handle lives in the expression evaluator and not in this zone, which is
    // the same seam `bootstrp`'s statistic crosses.
    using Model = std::function<bool(const ICoreMatrix& beta, const ICoreMatrix& x,
                                     ICoreMatrix& fitted, std::string& whyNot)>;

    // `[beta, r, J, CovB, MSE] = nlinfit(X, y, modelfun, beta0)`.
    //
    // ⚠ This one IS the situation T3.6 met with `fzero`, and its note says so
    // rather than hoping otherwise: Levenberg-Marquardt stops on `TolX` 1e-8
    // of the step or `TolFun` 1e-8 of the relative change in the sum of
    // squares, whichever comes first, and MATLAB's answer is where that
    // stopped. So this transcribes the ITERATION -- the 0.01 starting damping,
    // the tenfold increase on a failed step and the tenth on a good one, the
    // augmented `[J; sqrt(lambda*diag(J'J))]` least-squares solve, and
    // `statjacobian`'s forward difference at `eps^(1/3)` times each
    // coefficient -- rather than solving the problem its own way.
    static bool nonlinearFit(const ICoreMatrix& x, const ICoreMatrix& y, const Model& model,
                             const ICoreMatrix& start, ICoreMatrix& coefficients,
                             ICoreMatrix& residual, ICoreMatrix& jacobian,
                             ICoreMatrix& covariance, double& meanSquaredError,
                             std::string* whyNot = nullptr);

    // `[ypred, delta] = nlpredci(modelfun, X, beta, resid, ...)`. `newObservation`
    // is MATLAB's `"predopt", "observation"` (the interval for a future
    // OBSERVATION, which carries the error variance as well as the parameter
    // uncertainty); `simultaneous` is its `"simopt", "on"`.
    //
    // ⚠ Its finite-difference step is NOT `statjacobian`'s. Where `nlinfit`
    // falls back to `DerivStep * norm(beta)` for a coefficient that is zero,
    // `nlpredci.m` falls back to `DerivStep * sqrt(norm(beta))` -- the square
    // root of the norm. Two files, two rules, and the difference only shows
    // on a model fitted from a zero start.
    static bool nonlinearPredictionInterval(const Model& model, const ICoreMatrix& x,
                                            const ICoreMatrix& beta, const ICoreMatrix& residual,
                                            const ICoreMatrix& matrix, const bool& isCovariance,
                                            const double& alpha, const bool& newObservation,
                                            const bool& simultaneous, ICoreMatrix& predicted,
                                            ICoreMatrix& halfWidth, std::string* whyNot = nullptr);

    // `ci = nlparci(beta, resid, "covar", C)` or `("jacobian", J)`. Two ways
    // in, one answer: a standard error per coefficient, times `tinv` on
    // `n - p` degrees of freedom. `isCovariance` says which of the two the
    // matrix is.
    static bool nonlinearParameterInterval(const ICoreMatrix& beta, const ICoreMatrix& residual,
                                           const ICoreMatrix& matrix, const bool& isCovariance,
                                           const double& alpha, ICoreMatrix& interval,
                                           std::string* whyNot = nullptr);

    // `[b, se, pval, inmodel] = stepwisefit(X, y[, "penter", a][, "premove", b])`.
    //
    // ⚠ The answer is an ORDER of decisions, not a formula: at each step the
    // out-of-model column with the smallest p-value enters if that p is below
    // `penter`, otherwise the in-model column with the largest p leaves if
    // that p is above `premove`, and the loop stops when neither applies. So
    // what has to agree with MATLAB is the SEQUENCE, and the two p-values it
    // compares are computed on different degrees of freedom -- `dfe` for a
    // column in the model and `dfe - 1` for one outside it, because entering
    // it would spend one more.
    //
    // `scaled` is MATLAB's `"scale"`: the columns are standardised for the
    // arithmetic either way, and this says whether the coefficients are
    // reported on that scale or divided back.
    static bool stepwiseFit(const ICoreMatrix& x, const ICoreMatrix& y, const double& enter,
                            const double& remove, const bool& scaled,
                            ICoreMatrix& coefficients, ICoreMatrix& standardError,
                            ICoreMatrix& probability, ICoreMatrix& inModel,
                            std::string* whyNot = nullptr);

};
};

ICoreStatisticalResampling.h#

src/ICoreBlocks/ICoreMath/Statistics/ICoreStatisticalResampling.h

ICoreStatisticalResampling#

ICoreStatisticalResampling.h:31 · class · 1 declaration(s)

Toolbox row T3.23 -- jackknife, bootstrp, bootci, randsample and datasample: the five ways the Statistics Toolbox draws a new sample out of an old one.

class ICoreStatisticalResampling {
public:
    // The statistic, as the evaluator hands it over: the resampled inputs in,
    // the statistic flattened to a row out, and a reason when it cannot.
    using Statistic = std::function<bool(const std::vector<ICoreMatrix>&,
                                         std::vector<double>&, std::string&)>;

    // `jackstat = jackknife(jackfun, X1, ...)` -- one ROW per left-out
    // observation, in order, so row i is the statistic of the sample without
    // row i. Deterministic, and the only name here that is.
    static bool leaveOneOut(const std::vector<ICoreMatrix>& data, const Statistic& statistic,
                            ICoreMatrix& out, std::string* whyNot = nullptr);

    // `bootstat = bootstrp(nboot, bootfun, d1, ...)` -- `nboot` rows, each the
    // statistic of a sample of n rows drawn WITH replacement.
    static bool bootstrap(const size_t& draws, const std::vector<ICoreMatrix>& data,
                          const Statistic& statistic, ICoreMatrix& out,
                          std::string* whyNot = nullptr);

    // MATLAB's `bootci` interval types. `"student"` is refused by name: it is a
    // bootstrap inside a bootstrap, which is a different amount of computation
    // and a different row.
    enum class Interval { BiasCorrectedAccelerated, Percentile, Normal };

    static bool intervalOf(const std::string& word, Interval& type);

    // `ci = bootci(nboot, bootfun, d1, ...)` -- a 2 x k interval, the lower row
    // then the upper. The default type is the bias-corrected and accelerated
    // one, whose acceleration term is a JACKKNIFE of the same statistic, so a
    // `bootci` costs a `bootstrp` plus a `jackknife`.
    static bool bootstrapInterval(const size_t& draws, const std::vector<ICoreMatrix>& data,
                                  const Statistic& statistic, const double& alpha,
                                  const Interval& type, ICoreMatrix& out,
                                  std::string* whyNot = nullptr);

    // `y = randsample(n, k[, replace[, w]])` and `randsample(population, k, ...)`.
    // `population` empty means the first form, where the population is 1..n.
    static bool randomSample(const double& count, const ICoreMatrix& population,
                             const size_t& draws, const bool& withReplacement,
                             const ICoreMatrix& weights, ICoreMatrix& out,
                             std::string* whyNot = nullptr);

    // `y = datasample(data, k[, dim][, "Replace", tf][, "Weights", w])` -- the
    // same draw as `randsample`, applied to the ROWS (dim 1) or the COLUMNS
    // (dim 2) of a matrix. The default is WITH replacement, which is the
    // opposite of `randsample`'s default and is MATLAB's own inconsistency.
    static bool dataSample(const ICoreMatrix& data, const size_t& draws,
                           const size_t& dimension, const bool& withReplacement,
                           const ICoreMatrix& weights, ICoreMatrix& out,
                           std::string* whyNot = nullptr);

};
};

ICoreStatisticsPlots.h#

src/ICoreBlocks/ICoreMath/Statistics/ICoreStatisticsPlots.h

ICoreStatisticsPlots#

ICoreStatisticsPlots.h:32 · class · nested ProbabilityPlot, BoxSummary, HistogramFit, StepCurve · 3 declaration(s)

The NUMBERS behind the Statistics and Machine Learning Toolbox's plots (toolbox board row T3.25): histfit, normplot, probplot, qqplot, cdfplot and boxplot.

class ICoreStatisticsPlots {
public:
    // `normplot` / `probplot` / `qqplot` against the normal: the sorted sample
    // against the normal quantiles of its plotting positions, plus the
    // reference line MATLAB draws through the first and third quartiles of
    // both. `lineX`/`lineY` are the two ENDS of that line, extended to the
    // data range the way `normplot.m` extends it.
    struct ProbabilityPlot {
        std::vector<double> sorted;      // the sample, ascending
        std::vector<double> quantiles;   // norminv((i - 0.5)/n)
        double lineFirstX = 0.0, lineFirstY = 0.0;   // the reference line's ends
        double lineLastX  = 0.0, lineLastY  = 0.0;
    };
    static bool probabilityPlot(const ICoreMatrix& x, ProbabilityPlot& out, std::string& whyNot);

    // `qqplot(x, y)`: two samples against each other. The SHORTER sample sets
    // the positions and the longer one is read at those percentiles, which is
    // what `qqplot.m` does with `prctile` -- so two samples of different
    // length still answer one curve.
    static bool quantileQuantile(const ICoreMatrix& x, const ICoreMatrix& y,
                                 ProbabilityPlot& out, std::string& whyNot);

    // `boxplot`'s five numbers for one group, its notch and its outliers.
    // ⚠ `whiskerLow`/`whiskerHigh` are the ADJACENT VALUES (observations),
    // and they are clamped INTO the box when the most extreme non-outlier
    // falls between the quartiles -- `boxplot.m`'s own `wlo = min(wlo, p25)`,
    // which happens on small samples and looks like a bug when it is not.
    struct BoxSummary {
        double q1 = 0.0, median = 0.0, q3 = 0.0;
        double whiskerLow = 0.0, whiskerHigh = 0.0;
        double notchLow = 0.0, notchHigh = 0.0;
        std::vector<double> outliers;
    };
    static bool boxSummary(const ICoreMatrix& x, BoxSummary& out, std::string& whyNot);

    // `histfit`: the histogram MATLAB would draw (its own bin picker, at
    // `ceil(sqrt(n))` bins unless told otherwise) and the fitted density,
    // scaled to the histogram's AREA -- `n * binWidth * pdf`, which is what
    // makes the curve sit on the bars rather than under them.
    //
    // The curve is 100 points over the fitted distribution's three-sigma
    // range, `icdf(pd, [0.0013499 0.99865])` -- MATLAB's own two constants.
    struct HistogramFit {
        std::vector<double> edges;
        std::vector<double> counts;
        std::vector<double> curveX;
        std::vector<double> curveY;
        double mean = 0.0;
        double deviation = 0.0;
    };
    static bool histogramFit(const ICoreMatrix& x, const size_t& bins, HistogramFit& out,
                             std::string& whyNot);

    // `cdfplot`: the empirical CDF as the STAIRS MATLAB draws -- each distinct
    // value twice, so the curve rises vertically at the observation and runs
    // flat between two. MATLAB pads the ends with -Inf and +Inf; this answers
    // the finite part and the caller draws the flat ends it wants.
    struct StepCurve {
        std::vector<double> x;
        std::vector<double> y;
    };
    static bool empiricalCdfCurve(const ICoreMatrix& x, StepCurve& out, std::string& whyNot);
};
};

ICoreSummaryStatistics.h#

src/ICoreBlocks/ICoreMath/Statistics/ICoreSummaryStatistics.h

ICoreSummaryStatistics#

ICoreSummaryStatistics.h:52 · class · 1 declaration(s)

The six summary statistics of toolbox row T3.2 -- geomean, harmmean, trimmean, tabulate, crosstab and grpstats.

class ICoreSummaryStatistics {
public:
    // `geomean(x[, dim])` -- exp(mean(log(x))). A NEGATIVE entry is an error
    // on both sides (geomean.m raises stats:geomean:BadData); a zero is not,
    // and answers 0.
    static ICoreMatrix geometricMean(const ICoreMatrix& x, const int& dim,
                                     std::string* whyNot = nullptr);

    // `harmmean(x[, dim])` -- 1 ./ mean(1 ./ x). harmmean.m checks nothing, so
    // neither does this: a zero entry answers 0 (1/0 is Inf, its mean is Inf,
    // and 1/Inf is 0) and a sign mixture answers whatever the arithmetic gives.
    static ICoreMatrix harmonicMean(const ICoreMatrix& x, const int& dim,
                                    std::string* whyNot = nullptr);

    // `trimmean(x, percent[, flag[, dim]])`. The flag chooses how many samples
    // come off each end; see the class comment for why they differ.
    enum class Trim { Round, Floor, Weighted };
    static ICoreMatrix trimmedMean(const ICoreMatrix& x, const double& percent,
                                   const Trim& flag, const int& dim,
                                   std::string* whyNot = nullptr);

    // `tabulate(x)` -- the [value, count, percent] table of a numeric VECTOR,
    // NaN dropped. Two answer shapes; see the class comment.
    static ICoreMatrix tabulate(const ICoreMatrix& x, std::string* whyNot = nullptr);

    // `[table, chi2, p] = crosstab(x, y)` -- the contingency table of two
    // grouping vectors of the same length, with Pearson's statistic and its
    // upper-tail p-value. `chi2` and `p` are NaN when either variable has a
    // single level, which is MATLAB's answer for a table that cannot be
    // independent or dependent.
    static bool crossTabulation(const ICoreMatrix& x, const ICoreMatrix& y,
                                ICoreMatrix& table, double& chiSquare, double& pValue,
                                std::string* whyNot = nullptr);

    // `[means, sems, counts] = grpstats(x, group)` -- x's rows grouped by the
    // corresponding entry of `group`, one output row per group in ascending
    // group order. `sems` is `std(x, 0, 1) / sqrt(n)` per group, so a group of
    // one answers 0 and not NaN (MATLAB's std of one sample is 0).
    //
    // `counts` has x's WIDTH rather than one column -- a 4 x 2 x over two
    // groups answers a 2 x 2 of group sizes, not a 2 x 1 -- because MATLAB
    // applies its count function per column. Measured, not assumed.
    //
    // MATLAB's fourth output is `gname`, a CELL of group labels; the console
    // has no cell kind, so the caller is told to read the group values out of
    // `unique(group)` instead (T0.6, Z3).
    static bool groupStatistics(const ICoreMatrix& x, const ICoreMatrix& group,
                                ICoreMatrix& means, ICoreMatrix& sems, ICoreMatrix& counts,
                                std::string* whyNot = nullptr);

};
};