最小二乘法(又称最小平方法)是一种数学优化技术。它通过最小化误差的平方和寻找数据的最佳函数匹配。

利用最小二乘法可以简便地求得未知的数据,并使得这些求得的数据与实际数据之间误差的平方和为最小。

public enum RegressionLine
{
    kNone = 0,
    kLiner = 1,          // 线性
    kPower = 2,          // 幂次
    kLogarithm = 3,      // 对数
    kExponent = 4,       // 指数
    kMultiLinear = 5,    // 多项式
}

public abstract class RegressionAlgo
{
    public virtual RegressionLine GetCategory() { return RegressionLine.kNone; }

    protected double[] OriginX = null;
    protected double[] OriginY = null;
    // 设置点集
    // 外面应该剔除无效值以后再传进来,里面不做判断
    public virtual void SetOriginData(double[] x, double[] y)
    {
        this.OriginX = x;
        this.OriginY = y;
    }

    // 计算回归线参数
    public abstract bool CalculateRegression();

    // 根据X计算Y
    public abstract double CalculateValue(double x);

    // 获取参数列表
    public abstract double[] GetResult();
    // 获取R2
    public abstract double GetR2();
	
	// 获取公式字符串
    public abstract string GetFormula();
}

public class ShRegLinear : RegressionAlgo
{
    public override RegressionLine GetCategory() { return RegressionLine.kLiner; }

    public double A
    {
        get;
        private set;
    }

    public double B
    {
        get;
        private set;
    }

    public double R2
    {
        get;
        private set;
    }

    public ShRegLinear()
    {
        // Y = a * X + b
        this.A = 0.0;
        this.B = 0.0;
    }

    public override string GetFormula()
    {
        return string.Format("Y = {0:F4} * X + {1:F4} \r\n R2 ={2:F4}", this.A, this.B, this.R2);
    }

    public ShRegLinear(double[] param, double r2)
    {
        if (param.Length == 2)
        {
            this.A = param[0];
            this.B = param[1];
        }
        this.R2 = r2;
    }

    public override bool CalculateRegression()
    {
        if (this.OriginX == null || this.OriginX.Length < 3 ||
                this.OriginY == null || this.OriginX.Length != this.OriginY.Length)
            return false;

        // 最小二乘法 https://zh.wikipedia.org/wiki/%E6%9C%80%E5%B0%8F%E4%BA%8C%E4%B9%98%E6%B3%95
        double averX = this.OriginX.Average();
        double averY = this.OriginY.Average();

        double differencexy = 0.0;
        double sumxy = 0.0;
        double sumx = 0.0;
        double sumy = 0.0;
        for (int i = 0; i < this.OriginX.Length; i++)
        {
            differencexy += (this.OriginX[i] - averX) * (this.OriginY[i] - averY);
            sumxy += this.OriginX[i] * this.OriginY[i];
            sumx += Math.Pow((this.OriginX[i] - averX), 2);
            sumy += Math.Pow((this.OriginY[i] - averY), 2);
        }
        // 经验回归系数
        this.A = differencexy / sumx;
        this.B = averY - this.A * averX;
        this.R2 = Math.Pow(differencexy, 2) / (sumx * sumy);
        return true;
    }

    public override double CalculateValue(double x)
    {
        return (this.A * x + this.B);
    }

    public override double[] GetResult()
    {
        double[] result = new double[]
        {
            this.A,
            this.B,
        };
        return result;
    }

    public override double GetR2()
    {
        return this.R2;
    }
}

public class ShRegExponential : RegressionAlgo
{
    public override RegressionLine GetCategory() { return RegressionLine.kExponent; }

    public double A
    {
        get
        {
            return Math.Exp(this.RegLinear.A);
        }
    }

    public double B
    {
        get
        {
            return Math.Exp(this.RegLinear.B);
        }
    }

    public double R2
    {
        get
        {
            return this.RegLinear.R2;
        }
    }

    private ShRegLinear RegLinear = null;
    public ShRegExponential()
    {
        // Y = b * a ^ X
        this.RegLinear = new ShRegLinear();
    }

    public override string GetFormula()
    {
        return string.Format("Y = {1:F4} * {0:F4} ^ X \r\nR2 = {2:F4}", this.A, this.B, this.R2);
    }

    public ShRegExponential(double[] param, double r2)
    {
        if (param.Length == 2)
        {
            double[] temp = new double[2];
            temp[0] = Math.Log(param[0]);
            temp[1] = Math.Log(param[1]);
            this.RegLinear = new ShRegLinear(temp, r2);
        }
    }

    public override bool CalculateRegression()
    {
        if (this.OriginX == null || this.OriginX.Length < 3 ||
                this.OriginY == null || this.OriginX.Length != this.OriginY.Length)
            return false;

        List<double> x = new List<double>();
        List<double> y = new List<double>();
        for (int i = 0; i < this.OriginY.Length; i++)
        {
            if (this.OriginY[i] > 0)
            {
                x.Add(this.OriginX[i]);
                y.Add(Math.Log(this.OriginY[i]));
            }
        }

        this.RegLinear.SetOriginData(x.ToArray(), y.ToArray());
        return this.RegLinear.CalculateRegression();
    }

    public override double CalculateValue(double x)
    {
        return (this.B * Math.Pow(this.A, x));
    }

    public override double[] GetResult()
    {
        double[] result = new double[]
        {
            this.A,
            this.B,
        };
        return result;
    }

    public override double GetR2()
    {
        return this.R2;
    }
}

public class ShRegPower : RegressionAlgo
{
    public override RegressionLine GetCategory() { return RegressionLine.kPower; }

    public double A
    {
        get
        {
            return this.RegLinear.A;
        }
    }

    public double B
    {
        get
        {
            return Math.Exp(this.RegLinear.B);
        }
    }

    public double R2
    {
        get
        {
            return this.RegLinear.R2;
        }
    }

    private ShRegLinear RegLinear = null;
    public ShRegPower()
    {
        // Y = x^a * b
        this.RegLinear = new ShRegLinear();
    }

    public override string GetFormula()
    {
        return string.Format("Y = X ^ {0:F4} * {1:F4} \r\nR2 = {2:F4}", this.A, this.B, this.R2);
    }

    public ShRegPower(double[] param, double r2)
    {
        if (param.Length == 2)
        {
            double[] temp = new double[2];
            temp[0] = param[0];
            temp[1] = Math.Log(param[1]);
            this.RegLinear = new ShRegLinear(temp, r2);
        }
    }

    public override bool CalculateRegression()
    {
        if (this.OriginX == null || this.OriginX.Length < 3 ||
                this.OriginY == null || this.OriginX.Length != this.OriginY.Length)
            return false;

        List<double> x = new List<double>();
        List<double> y = new List<double>();
        for (int i = 0; i < this.OriginX.Length; i++)
        {
            if (this.OriginX[i] > 0 && this.OriginY[i] > 0)
            {
                x.Add(Math.Log(this.OriginX[i]));
                y.Add(Math.Log(this.OriginY[i]));
            }
        }

        this.RegLinear.SetOriginData(x.ToArray(), y.ToArray());
        return this.RegLinear.CalculateRegression();
    }

    public override double CalculateValue(double x)
    {
        return this.B * Math.Pow(x, this.A);
    }

    public override double[] GetResult()
    {
        double[] result = new double[]
        {
            this.A,
            this.B,
        };
        return result;
    }

    public override double GetR2()
    {
        return this.R2;
    }
}

public class ShRegLogarithm : RegressionAlgo
{
    public override RegressionLine GetCategory() { return RegressionLine.kLogarithm; }

    public double A
    {
        get
        {
            return this.RegLinear.A;
        }
    }

    public double B
    {
        get
        {
            return this.RegLinear.B;
        }
    }

    public double R2
    {
        get
        {
            return this.RegLinear.R2;
        }
    }

    private ShRegLinear RegLinear = null;
    public ShRegLogarithm()
    {
        // Y = a *(ln x) + b
        this.RegLinear = new ShRegLinear();
    }

    public override string GetFormula()
    {
        return string.Format("Y = {0:F4} * ln X + {1:F4} \r\nR2 = {2:F4}", this.A, this.B, this.R2);
    }

    public ShRegLogarithm(double[] param, double r2)
    {
        this.RegLinear = new ShRegLinear(param, r2);
    }

    public override bool CalculateRegression()
    {
        if (this.OriginX == null || this.OriginX.Length < 3 ||
                this.OriginY == null || this.OriginX.Length != this.OriginY.Length)
            return false;

        List<double> x = new List<double>();
        List<double> y = new List<double>();
        for (int i = 0; i < this.OriginX.Length; i++)
        {
            if (this.OriginX[i] > 0)
            {
                x.Add(Math.Log(this.OriginX[i]));
                y.Add(this.OriginY[i]);
            }
        }

        this.RegLinear.SetOriginData(x.ToArray(), y.ToArray());
        return this.RegLinear.CalculateRegression();
    }

    public override double CalculateValue(double x)
    {
        return this.A * Math.Log(x) + this.B;
    }

    public override double[] GetResult()
    {
        double[] result = new double[]
        {
            this.A,
            this.B,
        };
        return result;
    }

    public override double GetR2()
    {
        return this.R2;
    }
}

public class ShRegMultiLinear : RegressionAlgo
{
    public override RegressionLine GetCategory() { return RegressionLine.kMultiLinear; }

    public double[] Param = null;
    public double R2
    {
        get;
        private set;
    }

    public ShRegMultiLinear()
    {
        // Y = a[0] + a[1] * X + a[2] * X + ......
    }

    public override string GetFormula()
    {
        string text = "Y = ";
        for (int index = this.Param.Length - 1; index >= 0; index--)
        {
            if (Math.Abs(this.Param[index]) > 1e-5)
            {
                if (index > 0)
                {
                    text += string.Format("{0} * X^{1}", this.Param[index], index);
                }
                else
                {
                    text += string.Format("{0}", this.Param[index]);
                }
            }
        }
        text += string.Format("\r\nR2 = {0}", this.R2);
        return text;
    }

    public ShRegMultiLinear(double[] param, double r2)
    {
        this.Param = param;
        this.R2 = r2;
    }

    public void SetDegree(int ex)
    {
        // 多项式回归至少是2次
        if (ex < 2)
            ex = 2;
        this.Param = new double[ex + 1];
    }

    public override bool CalculateRegression()
    {
        // 几次多项式则至少要有这么多点
        if (this.OriginX == null || this.Param == null || this.OriginX.Length < this.Param.Length ||
                this.OriginY == null || this.OriginX.Length != this.OriginY.Length)
            return false;

        // 计算方程组的增广矩阵
        double[,] em = new double[this.Param.Length, this.Param.Length + 1];
        for (int i = 0; i < this.Param.Length; i++)
        {
            for (int j = 0; j < this.Param.Length; j++)
            {
                for (int k = 0; k < this.OriginX.Length; k++)
                {
                    em[i, j] += Math.Pow(this.OriginX[k], i + j);
                }
            }

            for (int k = 0; k < this.OriginX.Length; k++)
            {
                em[i, this.Param.Length] += this.OriginY[k] * Math.Pow(this.OriginX[k], i);
            }
        }

        // 求解方程(消元过程)
        for (int k = 0; k < this.Param.Length - 1; k++)
        {
            for (int i = k + 1; i < this.Param.Length; i++)
            {
                double t = -em[i, k] / em[k, k];
                for (int j = k + 1; j <= this.Param.Length; j++)
                {
                    em[i, j] += em[k, j] * t;
                }
            }
        }

        // 回代求解
        for (int i = this.Param.Length - 1; i >= 0; i--)
        {
            for (int j = i + 1; j < this.Param.Length; j++)
            {
                em[i, this.Param.Length] -= em[i, j] * em[j, this.Param.Length];
            }
            em[i, this.Param.Length] /= em[i, i];
        }

        // 获取参数
        this.Param[0] = em[0, this.Param.Length];
        for (int i = 1; i < this.Param.Length; i++)
        {
            this.Param[i] = em[i, this.Param.Length];
        }
        return true;
    }

    public override double CalculateValue(double x)
    {
        if (this.Param.Length < 3)
            return DefaultField.InvalidValue;

        double sum = 0.0;
        for (int index = 0; index < this.Param.Length; index++)
        {
            sum += this.Param[index] * Math.Pow(x, index);
        }
        return sum;
    }

    public override double[] GetResult()
    {
        return this.Param;
    }

    public override double GetR2()
    {
        double averY = this.OriginY.Average();

        double ssr = 0.0;
        double sse = 0.0;
        for (int i = 0; i < this.OriginX.Length; i++)
        {
            double yi = CalculateValue(this.OriginX[i]);
            // 回归平方和
            ssr += Math.Pow(yi - averY, 2);
            // 残差平方和
            sse += Math.Pow(yi - this.OriginY[i], 2);
        }
        return 1 - (sse / (ssr + sse));
    }
}