比较两组数据(x,y)

时间:2013-06-14 23:05:15

标签: c# dataset interpolation

我已经将数据存储在列表中,其坐标(xValues,yValues),如果我想比较(加,减,除......)那组数据到另一个,我必须知道我可以不比较xValues是否匹配(因为没有什么可比较的)。所以我需要在“缺失的”xValues之间线性插值,这些xValues实际存在于另一组中并生成新的点。请查看此图片:

enter image description here

红线上的青色方块表示存储的点(xValues2),并且(通常)它们与其他的xValues(xValues1)不匹配。绿线上的两个正方形是所需生成点的示例。有了它们,我可以毫无问题地使用这两张图。

对于线性插值这很简单:如果我有两个点(x0,y0)和(x1,y1)并且我想在它们之间添加一个给定“x2”的新点:

y2=y0+(x2-x0)*(y1-y0)/(x1-x0)

为了完成这项工作,我想我必须实现这样的事情:

  1. 创建新列表(xValuesNew,yValuesNew)。
  2. 在xValues1和xValues2(xValuesNew)之间建立联合。
  3. 检查原始xValues1和xValuesNew之间的差异。
  4. 对于找到的每个新值,使用上面写的公式生成“y”。
  5. 将4个步骤放入方法并再次使用,但现在使用set2。
  6. 我整天都在这,试图找到一个简单的解决方案,可能使用Linq或lambda表达式,但我不习惯使用它们,而且我对这些主题缺乏了解。请注意,此操作将经常进行,因此我必须使其不太重。我认为生成一个新列表而不是在原始中间插入点是个好主意。

    如果有人可以指导我一点,或者告诉我是否有数学库实际上这样做会很好。谢谢。


    编辑:对不起,如果我没有正确解释我。

    这里我有一个例子(在Excel中完成):

    enter image description here

    请注意,我不能直接将Series1和Series2(+)或任何其他操作添加在一起,因为它们中的X间距不同。所以我想要的是在需要时在Series1中生成一个新点。

    为此,我想简单地使用线性插值。假设我在系列1中有P1(0,40)和P2(0,60),但在连续2中我有一个点(1,10)。我需要在P1和P2之间用(1,50)坐标生成一个点P3。

    enter image description here

    enter image description here

    我试图用SkipWhile做这个并比较两个系列的下一个X值,如果series1的XValue较低,那么在newSeries中添加XValue和相应的YValue。否则使用XValue2生成Y并将其添加到newSeries。这是我的一次尝试(不起作用):

    List<double> series1X = new List<double> { 0, 2, 4, 6, 8 };
            List<double> series1Y = new List<double> { 120, 100, 110, 105, 70 };
            List<double> series2X = new List<double> { 0, 1, 7, 8,9 };
    
            List<double> newSeries1X = new List<double>();
            List<double> newSeries1Y = new List<double>();
    
      double lastX1 = series1X[series1X.Count()-1];
            int i = 0;
    
            while (next1X <= lastX1)
            {
    
                next2X = series2X.SkipWhile(p => p <= next1X).First();
                Console.WriteLine(next2X.ToString());
    
                if (next1X <= next2X)
                {
    
                    newSeries1X.Add(series1X[i]);
                    newSeries1Y.Add(series1Y[i]);
                }
    
                if (next2X < next1X)
                {
    
                    while (next2X < next1X)
                    {
                        newSeries1X.Add(next2X);
                        newY = series1Y[i] + (next2X - series1X[i]) * (series1Y[i + 1] - series1Y[i]) / (series1X[i + 1] - series1X[i]);
                        newSeries1Y.Add(newY);
    
                        next2X = series2X.SkipWhile(p => p <= next2X).First();
                    }
                }
    
    
                next1X = series1X.SkipWhile(p => p <= next2X).First();
                Console.WriteLine(next1X.ToString());
                i++;
    
            }
    

    使用Zip方法执行此操作真是太棒了。但我不知道如何在谓词中写出这个条件。

2 个答案:

答案 0 :(得分:2)

首先,我可能会使用一个合适的'point'类,它包含 x y 坐标,而不是每个坐标的两个单独列表。然后,您可以使用Zip方法快速迭代它们:

IEnumerable<PointF> points0 = ...
IEnumerable<PointF> points0 = ...
float x2 = ...
IEnumerable<PointF> newPoints = point0.Zip(points1, 
    (p0, p1) => new PointF(p0.X, p0.Y + (x2-p0.X) * (p1.Y-p0.Y) / (p1.X-p0.X)));

这样可以轻松地根据输入数据计算一组新点。如果您只关心单个y值,您仍然可以使用当前数据执行此操作,这看起来很奇怪:

IEnumerable<double> y2values = 
    xValues1.Zip(yValues1, (x, y) => new { x, y }).Zip(
        xValues2.Zip(yValues2, (x, y) => new { x, y }),
        (p0, p1) => p0.y + (x2-p0.x) * (p1.y-p0.y) / (p1.x-p0.x));

我道歉,如果在编写这个答案的过程中,我以某种方式破坏了你的数学。


<强>更新

既然我已经更好地掌握了你想要做的事情,我认为任何Linq方法都不会很正常。我在这里使用索引得出了什么:

List<double> series1X = new List<double> {   0,   2,   4,   6,  8 };
List<double> series1Y = new List<double> { 120, 100, 110, 105, 70 };
List<double> series2X = new List<double> {   0,   1,   7,   8,  9 };

// in the worst case there are n + m new points
List<double> newSeries1X = new List<double>(series1X.Count + series2X.Count);
List<double> newSeries1Y = new List<double>(series1X.Count + series2X.Count);

int i = 0, j = 0;
for ( ; i < series1X.Count && j < series2X.Count; )
{
    if (series1X[i] <= series2X[j])
    {
        newSeries1X.Add(series1X[i]);
        newSeries1Y.Add(series1Y[i]);
        if (series1X[i] == series2X[j])
        {
            j++;
        }
        i++; 
    }
    else
    {
        int k = (i == 0) ? i : i - 1;
        // interpolate
        double y0 = series1Y[k];
        double y1 = series1Y[k + 1];
        double x0 = series1X[k];
        double x1 = series1X[k + 1];
        double y = y0 + (y1 - y0) * (series2X[j] - x0) / (x1 - x0);
        newSeries1X.Add(series2X[j]);
        newSeries1Y.Add(y);
        j++;
    }
}
for ( ; i < series1X.Count; i++)
{
    newSeries1X.Add(series1X[i]);
    newSeries1Y.Add(series1Y[i]);
}
for ( ; j < series2X.Count; j++)
{
    // interpolate
    double y0 = series1Y[i - 2];
    double y1 = series1Y[i - 1];
    double x0 = series1X[i - 2];
    double x1 = series1X[i - 1];
    double y = y0 + (y1 - y0) * (series2X[j] - x0) / (x1 - x0);
    newSeries1X.Add(series2X[j]);
    newSeries1Y.Add(y);
}

输出

newSeries1X = {    0,    1,    2,    4,    6,    7,    8,    0 }
newSeries1Y = {  120,  110,  100,  110,  105, 87.5,   70, 52.5 }

此解决方案通过从第一对/最后一对点向外线性“投射”数据来处理第一个series2X[0] < series1X[0]series2X[n] > series1X[m]的情况。

这是使用枚举器(主要是)的另一种解决方案,但它并不像我希望的那样优雅。它可能会有所改善:

bool hasS1 = true, hasS2 = true, preinterp = true;
double x0 = 0, y0 = 0, x1 = 0, y1 = 0, x = 0, y = 0;
using(var s1xEnumerator = series1X.GetEnumerator())
using(var s1yEnumerator = series1Y.GetEnumerator())
using(var s2xEnumerator = series2X.GetEnumerator())
{
    hasS1 = s1xEnumerator.MoveNext();
    hasS2 = s2xEnumerator.MoveNext();
    s1yEnumerator.MoveNext();
    while(hasS1 && hasS2)
    {
        x1 = s1xEnumerator.Current;
        y1 = s1yEnumerator.Current;
        x = s2xEnumerator.Current;

        if (x1 <= x)
        {
            newSeries1X.Add(x1);
            newSeries1Y.Add(y1);
            hasS1 = s1xEnumerator.MoveNext();
            s1yEnumerator.MoveNext();
            preinterp = false;
            if (hasS1)
            {
                x0 = x1;
                y0 = y1;
            }
            if (x1 == x)
            {
                hasS2 = s2xEnumerator.MoveNext();
            }
        }
        else
        {
            // we have to look ahead to get the next interval to interpolate before x0
            if (preinterp)
            {
                x0 = x1;
                y0 = y1;
                x1 = series1X[1]; // can't peek with enumerator
                y1 = series1Y[1]; 
                preinterp = false;
            }

            y = y0 + (y1 - y0) * (x - x0) / (x1 - x0);
            newSeries1X.Add(x);
            newSeries1Y.Add(y);
            hasS2 = s2xEnumerator.MoveNext();
        }
    }

    while(hasS1)
    {
        newSeries1X.Add(s1xEnumerator.Current);
        newSeries1Y.Add(s1yEnumerator.Current);
        hasS1 = s1xEnumerator.MoveNext();
        s1yEnumerator.MoveNext();
    }

    while(hasS2)
    {
        x = s2xEnumerator.Current;
        y = y0 + (y1 - y0) * (x - x0) / (x1 - x0);
        newSeries1X.Add(x);
        newSeries1Y.Add(y);
        hasS2 = s2xEnumerator.MoveNext();
    }
}

答案 1 :(得分:0)

对于使用不同间距的两个系列,我首先需要在第一组中生成点,然后在第二组中生成点(使用相同的方法),最后在点到点之间生成点。

以下是该方法的代码:

using OxyPlot.Series;
using OxyPlot;

namespace Algorithm1
{

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            List<DataPoint> S1 = new List<DataPoint> ();
            List<DataPoint> S2 = new List<DataPoint>();
            List<DataPoint> NS1 = new List<DataPoint>();

            S1.Add(new DataPoint(4, 10));
            S1.Add(new DataPoint(6, 20));
            S1.Add(new DataPoint(8, 15));
            S1.Add(new DataPoint(9, 70));
            S1.Add(new DataPoint(10, 5));

            S2.Add(new DataPoint(1, 0));
            S2.Add(new DataPoint(2, 0));
            S2.Add(new DataPoint(3, 0));
            S2.Add(new DataPoint(6, 0));
            S2.Add(new DataPoint(7, 0));
            S2.Add(new DataPoint(8.1, 0));
            S2.Add(new DataPoint(8.2, 0));
            S2.Add(new DataPoint(8.3, 0));
            S2.Add(new DataPoint(8.4, 0));
            S2.Add(new DataPoint(9, 0));
            S2.Add(new DataPoint(9.75, 0));
            S2.Add(new DataPoint(11, 0));
            S2.Add(new DataPoint(12, 0));
            S2.Add(new DataPoint(16, 0));

           NS1 = GetMiddlePoints(S1, S2);
           foreach (DataPoint pointin NS1)
           {
               MessageBox.Show( point.X.ToString()+" : "+ point.Y.ToString());
           }


        }

        #region GetMiddlePoints
        private List<DataPoint> GetMiddlePoints(List<DataPoint> S1, List<DataPoint> S2)
        {
            List<DataPoint> NS1 = new List<DataPoint>();

            int i = 0;
            int j = S2.TakeWhile(p => p.X < S1[0].X).Count();


            int PointsInS1 = S1.Count;
            int PointsInS2 = S2.Count;

            DataPoint newPoint = new DataPoint();


            while (i < PointsInS1 )
            {
                if (j < PointsInS2 )
                {
                    if (S1[i].X < S2[j].X)
                    {
                        newPoint = S1[i];
                        NS1.Add(newPoint );
                        i++;
                    }

                    else if (S1[i].X == S2[j].X)
                    {
                        newPoint = S1[i];
                        NS1.Add(newPoint );
                        i++;
                        j++;
                    }

                    else if (S1[i].X > S2[j].X)
                    {
                        newPoint .X = S2[j].X;
                        newPoint .Y = S1[i-1].Y + (S2[j].X - S1[i-1].X) * (S1[i].Y - S1[i-1].Y) / (S1[i].X - S1[i-1].X);
                        NS1.Add(newPoint );
                        j++;

                    }

                }

                if (j == PointsInS2 )
                {
                    newPoint = S1[i];
                    NS1.Add(newPoint );
                    i++;
                }


            }


            return NS1;

        }

        #endregion

    }
}