如何使回归线在对数尺度上更加准确?

问题描述 投票:0回答:1

提供的代码绘制了一些数据点和相应的回归线。

回归线穿过或接近线性尺度上的大多数数据点。

但是,在对数尺度上,这条线看起来不太准确。

此外,线条的长度也变短了。

如何使回归线在线性尺度和对数尺度上看起来相同?

using System;
using System.Collections.Generic;
using ZedGraph;
using System.Drawing;
using System.IO;
using System.Linq;

class GenerateRegressionLine
{
    static void Main()
    {
        string dataFilePath = @"output.txt";
        Tuple<List<double>, List<double>> givenLine = Fit.ReadDataFromFile(dataFilePath);

        Tuple<List<double>, List<double>> regressionLine = CreateRegressionLine(givenLine.Item1, givenLine.Item2);

        
        ZedGraphControl zgc = new ZedGraphControl();
        zgc.Size = new Size(1200, 800);

        GraphPane myPane = zgc.GraphPane;
        myPane.Title.Text = "Line Plot";
        myPane.XAxis.Title.Text = "X Axis";
        myPane.YAxis.Title.Text = "Y Axis";

        myPane.XAxis.Type = AxisType.Log;
        myPane.YAxis.Type = AxisType.Log;

        PointPairList givenLinePPL = new PointPairList(givenLine.Item1.ToArray(), givenLine.Item2.ToArray());
        LineItem givenLineCurve = myPane.AddCurve("Given Line", givenLinePPL, Color.Green, SymbolType.None);

        PointPairList regressionLinePPL = new PointPairList(regressionLine.Item1.ToArray(), regressionLine.Item2.ToArray());
        LineItem regressionLineCurve = myPane.AddCurve("Regression Line", regressionLinePPL, Color.Red, SymbolType.None);


        zgc.AxisChange();
        zgc.Invalidate();

        string directory = Path.GetDirectoryName(dataFilePath);

        string imagePath = Path.Combine(directory, "DrawRegressionLine.png");

        zgc.GetImage().Save(imagePath, System.Drawing.Imaging.ImageFormat.Png);
    }

    private static Tuple<double, double> CalculateLinearRegressionCoefficients(List<double> xList, List<double> yList)
    {
        if (xList == null || yList == null || xList.Count != yList.Count)
            throw new ArgumentException("Lists must be non-null and have the same number of elements.");

        double xSum = 0, ySum = 0, xySum = 0, x2Sum = 0;
        int count = xList.Count;

        for (int i = 0; i < count; i++)
        {
            double x = xList[i];
            double y = yList[i];
            xSum += x;
            ySum += y;
            xySum += x * y;
            x2Sum += x * x;
        }

        double slope = (count * xySum - xSum * ySum) / (count * x2Sum - xSum * xSum);
        double intercept = (ySum - slope * xSum) / count;

        return Tuple.Create(intercept, slope);
    }

    public static Tuple<List<double>, List<double>> CreateRegressionLine(List<double> xList, List<double> yList)
    {
        // Calculate the regression coefficients
        var coefficients = CalculateLinearRegressionCoefficients(xList, yList);

        List<double> xVals = new List<double>();
        List<double> yVals = new List<double>();

        double intercept = coefficients.Item1;
        double slope = coefficients.Item2;

        double startX = xList[0];
        double endX = xList[xList.Count - 1];

        xVals.Add(startX);
        yVals.Add(intercept + slope * startX);

        xVals.Add(endX);
        yVals.Add(intercept + slope * endX);

        return Tuple.Create(xVals, yVals);
    }
}


c# plot charts linear-regression zedgraph
1个回答
0
投票

简单的回答是,您需要首先了解对数刻度图如何更好地工作。与此相关的是,我赞成并回答这个问题,因为我记得在我的菜鸟时代也有过类似的问题,当时世界还是新的。这让我相信这个问题并不是那么糟糕:)

左侧的点似乎被忽略有两个原因。

  1. y 值较低的点与右侧的点具有相同的权重或 y 不确定性。在线性图上,这意味着每个点的拟合的“摆动空间”看起来是均匀的。对数图则不然。看看左边和右边巨大的误差线。它们的长度均为 +/-30,以提高可视性。在最小二乘拟合中,权重的比例关系很重要,而不是绝对大小。
  2. 线性空间中的线不一定是对数空间中的线。唯一的时间是斜率恰好为 1 时。您的
    CreateRegressionLine
    具有误导性,因为它只输出第一个和最后一个点。我在下面输出了一个更密集的数组,您可以在其中看到相同数据的双对数图在对数空间中不是线性的。

那么该怎么办呢?有几种简单的方法。具体选择哪种方法取决于您到底想要什么,但目前还不清楚,甚至可能对您来说也是如此。

  1. 在对数空间中进行线性回归。这将使它成为对数空间中的一条实际线,并且那里的权重将是均匀的。权重在对数空间中是对称的,因此这些图只是近似值:

  2. 进行回归时,将权重设置为与对数比例成比例。换句话说,对于较大的 y 值,最小二乘法的 1-sigma 不确定性应该大得多,而对于小值,最小二乘法的 1-sigma 不确定性应该小得多,以便在对数尺度上显得几乎均匀。一个简单但不完美的实现是采用

    y
    的倒数作为 1-sigma 权重。这将在线性空间中形成一条线,但不是一条很好的线,并且对数空间中的曲线仍然不会是一条线,但可能是更令人满意的拟合。误差线显示为
    y
    (为了可见性缩小了 20 倍),因为不确定性与权重成反比,而权重只是
    1 / y

    对于此选项,您必须实现加权最小二乘法,维基百科对此进行了解释:https://en.wikipedia.org/wiki/Weighted_least_squares


我对 C# 知之甚少,所以我在这里用 python 完成了所有的绘图。为了完整性,代码如下。我希望您能够相对轻松地理解它。如果有什么具体的问题让您感到困扰,请告诉我。

from matplotlib import pyplot as plt
import numpy as np

np.random.seed(0)

x = np.arange(0, 2000, 100)
y = np.random.uniform(1.0, 100.0, x.shape).cumsum()

fit1 = np.polyfit(x, y, deg=1)
r1 = np.polyval(fit1, x)

fig1, ax1 = plt.subplots(1, 2)

ax1[0].set_title('Linear')
ax1[0].errorbar(x, y, 30, label='data')
ax1[0].plot(x, r1, label='fit')
ax1[0].legend()

ax1[1].set_yscale('log')
ax1[1].set_title('Log')
ax1[1].errorbar(x, y, 30, label='data')
ax1[1].plot(x, r1, label='fit')
ax1[1].legend()


fit2 = np.polyfit(x, np.log(y), deg=1)
r2 = np.exp(np.polyval(fit2, x))
bars = np.stack((np.exp(np.log(y) - 1), np.exp(np.log(y) + 1)))

fig2, ax2 = plt.subplots(1, 2)

ax2[0].set_title('Linear')
ax2[0].errorbar(x, y,  bars, label='data')
ax2[0].plot(x, r2, label='fit')
ax2[0].legend()

ax2[1].set_yscale('log')
ax2[1].set_title('Log')
ax2[1].errorbar(x, y, bars, label='data')
ax2[1].plot(x, r2, label='fit')
ax2[1].legend()

fit3 = np.polyfit(x, y, deg=1, w=1.0 / y)
r3 = np.polyval(fit3, x)

fig3, ax3 = plt.subplots(1, 2)

ax3[0].set_title('Linear')
ax3[0].errorbar(x, y, y / 20, label='data')
ax3[0].plot(x, r3, label='fit')
ax3[0].legend()

ax3[1].set_yscale('log')
ax3[1].set_title('Log')
ax3[1].errorbar(x, y, y / 20, label='data')
ax3[1].plot(x, r3, label='fit')
ax3[1].legend()

plt.show()
© www.soinside.com 2019 - 2024. All rights reserved.