在数组(或两个数组)之间进行插值

8
我正在寻找一个Java库或一些帮助来编写我的插值函数。也就是说,我有两个可能大小不同但有序的双精度数组。我需要能够估计中间值,并插入以使两个数组变为相同大小。实际上,在插值中出现的点的总数是两个数组大小之和减1。但每个数组的范围必须保持不变,因此不需要外推。
例如,a1 = [1, 4, 9, 16, 25, 36] 和 a2 = [6, 9, 14, 30],结果可以是:
a1 = [1, 2.25, 4, 6.25, 9, 12.25, 16, 25, 36] 和 a2 = [6, 6.5625, 7.25, 9, 10.0625, 11.25, 14, 25.25, 30]
这些示例是 f(x) = x^2 和 g(x) = x^2 + 5,但可以轻松地是任何多项式-关键在于能够从数据集中良好地估计/近似函数,以提供足够好的插值。在这里,x值只是输入数组的索引。在输出中,只有y值很重要。

你的问题有点不清楚。你能给我们举一个具体的简单例子吗? - skaffman
我已经进行了编辑以展示一个例子,虽然它不一定是函数可能/会给出的输出,但我认为它展示了一个可接受的输出。 - Robert
8个回答

14
其他答案给出的是线性插值--这些并不适用于复杂的非线性数据。您需要一个样条拟合(样条插值)才行。

样条拟合使用数据集中的一组控制点来描述数据区域,然后在控制点之间应用多项式插值。更多的控制点可以给您提供更准确的拟合,较少的控制点可以给您提供更一般的拟合。样条比线性拟合更准确,比一般回归拟合更快,比高阶多项式更好,因为它不会在控制点之间做疯狂的事情。

我想不起名字了,但是Java中有一些优秀的拟合库--我建议您寻找一个而不是编写自己的函数。


**编辑:可能有用的库:**

** 可能有用的理论/代码:**

  • 带代码的样条插值小程序:link
  • Arkan用于将折线拟合为贝塞尔曲线的样条插值
  • Theory的样条插值理论和一些拟合数学。如果库无法解决问题,更多的数学知识可能会有所帮助。

是的,这正是我想要的...更具体地说,那些库是什么,以及它们在哪里。 - Robert
我已经添加了一些库的链接,以及关于样条曲线的代码和理论,如果这些库不可用。如果对您有帮助,请不要忘记接受答案! - BobMcGee

4

设计用于一维数据数组

import java.util.ArrayList;

public class Interpolator {

public static Float CosineInterpolate(Float y1,Float y2,Float mu)
{
    double mu2;

    mu2 = (1.0f-Math.cos(mu*Math.PI))/2.0f;
    Float f_mu2 = new Float(mu2);
    return(y1*(1.0f-f_mu2)+y2*f_mu2);
}

public static Float LinearInterpolate(Float y1,Float y2,Float mu)
{
    return(y1*(1-mu)+y2*mu);
}


public static Float[] Interpolate(Float[] a, String mode) {

    // Check that have at least the very first and very last values non-null
    if (!(a[0] != null && a[a.length-1] != null)) return null;

    ArrayList<Integer> non_null_idx = new ArrayList<Integer>();
    ArrayList<Integer> steps = new ArrayList<Integer>();

    int step_cnt = 0;
    for (int i=0; i<a.length; i++)
    {
        if (a[i] != null)
        {
            non_null_idx.add(i);
            if (step_cnt != 0) {
                steps.add(step_cnt);
                System.err.println("aDDed step >> " + step_cnt);
            }
            step_cnt = 0;
        }
        else
        {
            step_cnt++;
        }
    }

    Float f_start = null;
    Float f_end = null;
    Float f_step = null;
    Float f_mu = null;

    int i = 0;
    while (i < a.length - 1) // Don't do anything for the very last element (which should never be null)
    {
        if (a[i] != null && non_null_idx.size() > 1 && steps.size() > 0)
        {
            f_start = a[non_null_idx.get(0)];
            f_end = a[non_null_idx.get(1)];
            f_step = new Float(1.0) / new Float(steps.get(0) + 1);
            f_mu = f_step;
            non_null_idx.remove(0);
            steps.remove(0);
        }
        else if (a[i] == null)
        {
            if (mode.equalsIgnoreCase("cosine"))
                a[i] = CosineInterpolate(f_start, f_end, f_mu);
            else
                a[i] = LinearInterpolate(f_start, f_end, f_mu);
            f_mu += f_step;
        }
        i++;
    }

    return a;
}
}

不知道这是否有帮助... 这段代码编写非常快,因此如果有更好/更高效的方法来完成相同的操作,请感谢贡献。

用法:

input : Float[] a = {1.0f, null, null, 2.0f, null, null, null, 15.0f};

call : Interpolator.Interpolate(a, "Linear");

output : 1.0|1.3333333|1.6666667|2.0|5.25|8.5|11.75|15.0

返回一个空值。 - 3xCh1_23
@3xCh1_23 数组中的最后一个值不应为 null。 - Developer66

3
我知道这是一个老回答,但它是在搜索Java插值时的第一个Google搜索结果。 接受的答案提供了一些有用的链接,但JMSL必须购买,并且JSpline +网站看起来不可靠。
Apache Commons Math具有线性和样条插值的实现,看起来简单、功能齐全且可靠。 http://commons.apache.org/proper/commons-math/

1

简单的线性插值可以使用类似以下的方式进行计算:

Point2D interp1_lin(Point2D p1, Point2D p2, double x) {
 //Pre conditions
assert p1.x<x;
assert x<p2.x;
//Calculate slope from p1 to p2
double m = (p2.x-p1.x)/(p2.y-p1.y);
//Calculate y position of x
double y = (x-p1.x)*m+p1.y;
//create new point
return new Point2D.Double(x,y);
}

这有帮助吗?


0

一维数组线性插值器的轻量级版本:

public static float[] interpolate(float[] data) {
    int startIdx = -1;
    float startValue = 0f;
    float element;
    for (int i = 0; i < data.length - 1; i++) {
        element = data[i];
        if (element != 0f) {
            if (startIdx != -1) {
                doInterpolate(startValue, element, startIdx + 1, i - startIdx - 1, data);
            }
            startValue = element;
            startIdx = i;
        }
    }
    return data;
}

private static void doInterpolate(float start, float end, int startIdx, int count, float[] data) {
    float delta = (end - start) / (count + 1);
    for (int i = startIdx; i < startIdx + count; i++) {
        data[i] = start + delta * (i - startIdx + 1);
    }
}

0

在进行样条拟合和多项式拟合时一定要非常小心。这两种方法可能会导致荒谬的行为,从而破坏对(被认为是数据呈现的)数据的许多使用。

任何使用数据导数(斜率)的方法都可能被完全破坏。

最好的方法是绘制数据图,理解它的变化,然后再进行线性、多项式、对数-对数回归;完成后,您应该将拟合结果与原始数据进行比较,并确保您看到了合理的一致性。跳过此比较步骤是一个非常糟糕的想法。

某些数据集不适合拟合多项式、对数-对数等曲线;如果您的数据点适当地分布在数据范围内,那么使用分段插值(线性或多项式等)也没有问题。重申一遍,如果您使用分段插值,请避免使用任何使用导数/斜率的方法来处理分段插值,因为它会产生不连续性,导致事情变得混乱。


0

1
还有专门为此目的设计的LinearInterpolator。 - Slava

0

你需要获取与y值对应的x值。否则,没有算法能够确定[1, 16, 81]是x^2对应[1, 4, 9]还是x^4对应[1, 2, 3]。你会插值六个值还是不插值?

然后,当你有了x值之后,你可以使用某种插值方法(线性、三次样条等)来近似缺失的值。


好的,我没有仔细考虑我的例子 - 假设每个数组索引都是输入的x值 - 然后在插值之后,我们不再关心x值。 - Robert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接