15.1.252. tablet_qt/maths/logisticdescriptives.h

/*
    Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
    Created by Rudolf Cardinal (rnc1001@cam.ac.uk).

    This file is part of CamCOPS.

    CamCOPS 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.

    CamCOPS 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 CamCOPS. If not, see <https://www.gnu.org/licenses/>.
*/

#pragma once

#include <QVector>
#include "maths/include_eigen_dense.h"  // IWYU pragma: keep


class LogisticDescriptives
{
    // Deals with multiple ways of specifying a simple logistic regression
    // equation with a SINGLE predictor.
    //
    // This class just stores the coefficients. It can be initialized directly
    // with them, or given data with which to perform the regression (via
    // the LogisticRegression class).

public:
    // "Invalid" initialization:
    LogisticDescriptives();

    // Initialize from coefficients:
    LogisticDescriptives(const Eigen::VectorXd& coefficients);
    LogisticDescriptives(const QVector<qreal>& coefficients);
    LogisticDescriptives(double intercept, double slope);

    // Initialize from data:
    LogisticDescriptives(const QVector<qreal>& x, const QVector<int>& y,
                         bool verbose = false);

    // OK?
    bool ok() const;

    // Values:
    double b0() const;
    double b1() const;
    double intercept() const;
    double slope() const;
    double k() const;
    double theta() const;
    double x50() const;

    // Prediction:
    double p(double x) const;
    double x(double p) const;

protected:
    void setFromGlmCoefficients(double b0, double b1);

protected:
    bool m_ok;

    double m_b0;
    double m_b1;

    /*

    1.  These parameters are the GLM versions. We start with these.
        The GLM, ignoring error terms, is:

            Y = logistic(Xb) = logistic(b0 + b1*x)
            logit(Y) = Xb = b0 + b1*x

        The Y value, here, is the probability p, so:

            p = logistic(b0 + b1*x)
              = 1 / (1 + exp(-[b0 + b1*x]));
            logit(p) = b0 + b1*x

        Reversing:

            x = (logit(p) - b0) / b1

        Or, reversing in full:

            P = 1 / (1 + exp(-intercept - slope * X))
            1 = P + P * exp(-intercept - slope * X)
            -intercept - slope*X = ln((1 - P) / P)
            intercept + slope * X = ln(P / (1 - P))
            X = (ln(P / (1 - P)) - intercept) / slope

    2.  From an alternative formulation:
        These parameters define a linear equation in logits,
            L(X) = intercept + slope * X
        The logistic function itself is
            P = plogis(L) = 0.5 * (1 + tanh(L/2)) = 1 / (1 + exp(-L))
        So that's
            P = 1 / (1 + exp(-intercept - slope * X))
        Comparing to Lecluyse & Meddis (2009)'s function,
            p = 1 / (1 + exp(-k(X - theta)))
              = 1 / (1 + exp(-k*X + k*theta))),
        we have
            k = slope
        and
            theta = -intercept/k = -intercept/slope

        Comparing back to the GLM function:

            intercept = b0
            slope = b1
            k = slope = b1
            theta = -intercept/k = -b0/b1      [... since k*theta = -intercept]

    3.  We will have p = 0.5 when

            0.5 = 1 / (1 + exp(-[b0 + b1*x]))
            1 + exp(-[b0 + b1*x]) = 2
            exp(-[b0 + b1*x]) = 1
            -[b0 + b1*x] = 0
            b0 + b1*x = 0
            x = -b0/b1
              = theta

    */
};