在numpy数组中插值NaN值

77

有没有一种快速的方法可以将numpy数组中所有的NaN值替换为(比如)线性插值后的值?

例如,

[1 1 1 nan nan 2 2 nan 0]

会被转换为

[1 1 1 1.3 1.6 2 2  1  0]

8
抱歉打扰老帖子了,但我认为这很值得。一个更简单的方法是使用pandas和numpy:pd.DataFrame([1, 3, 4, np.nan, 6]).interpolate().values.ravel().tolist() - Francisco Zamora-Martínez
7
我发现 pd.Series([1, 3, 4, np.nan, 6]).interpolate().values.tolist() 更加简洁。 - Alfe
截至pandas 1.2.4版本:pd.Series([1, 3, 4, np.nan, 6]).interpolate().tolist() 更短。 - Shadi
13个回答

118

首先,我们定义一个简单的辅助函数,以便更容易处理NaNs的索引和逻辑索引:

import numpy as np

def nan_helper(y):
    """Helper to handle indices and logical indices of NaNs.

    Input:
        - y, 1d numpy array with possible NaNs
    Output:
        - nans, logical indices of NaNs
        - index, a function, with signature indices= index(logical_indices),
          to convert logical indices of NaNs to 'equivalent' indices
    Example:
        >>> # linear interpolation of NaNs
        >>> nans, x= nan_helper(y)
        >>> y[nans]= np.interp(x(nans), x(~nans), y[~nans])
    """

    return np.isnan(y), lambda z: z.nonzero()[0]

现在可以像这样使用nan_helper(.):

>>> y= array([1, 1, 1, NaN, NaN, 2, 2, NaN, 0])
>>>
>>> nans, x= nan_helper(y)
>>> y[nans]= np.interp(x(nans), x(~nans), y[~nans])
>>>
>>> print y.round(2)
[ 1.    1.    1.    1.33  1.67  2.    2.    1.    0.  ]

虽然一开始为了执行诸如此类的操作而指定一个单独的函数可能有些过度,但这样做仍然是值得的。

>>> nans, x= np.isnan(y), lambda z: z.nonzero()[0]

这将最终带来回报。

因此,每当您处理与 NaN 相关的数据时,只需将所有(新的 NaN 相关的)所需功能封装在某些特定的帮助器函数下。由于它遵循易于理解的惯用语法,因此您的代码库将更具连贯性和可读性。

插值确实是一个不错的上下文,可以了解如何处理 NaN,但类似的技术也应用于其他各种上下文中。


29

我想出了这段代码:

import numpy as np
nan = np.nan

A = np.array([1, nan, nan, 2, 2, nan, 0])

ok = -np.isnan(A)
xp = ok.ravel().nonzero()[0]
fp = A[-np.isnan(A)]
x  = np.isnan(A).ravel().nonzero()[0]

A[np.isnan(A)] = np.interp(x, xp, fp)

print A

它打印出

 [ 1.          1.33333333  1.66666667  2.          2.          1.          0.        ]

5
很抱歉,@fmonegaglia,这个脚本只能在二维数组的一个轴上进行插值,而不是二维插值。需要对二维数组中的NaN值进行插值的问题已经成为了scipy的一个问题:https://github.com/scipy/scipy/issues/1682 - E. Douglas
从引用的问题中,您可以直接使用astropy的卷积函数。 - ThatOneDude
5
用波浪线(~)代替横杠(-),以使其正常工作(可能会随着版本的变化而有所不同)。 - hootnot

13

只需使用numpy的逻辑与和where语句,即可应用一维插值。

import numpy as np
from scipy import interpolate

def fill_nan(A):
    '''
    interpolate to fill nan values
    '''
    inds = np.arange(A.shape[0])
    good = np.where(np.isfinite(A))
    f = interpolate.interp1d(inds[good], A[good],bounds_error=False)
    B = np.where(np.isfinite(A),A,f(inds))
    return B

3
这不处理序列开头或结尾的 NaN 值。 - EricP

10

对于二维数据,SciPy的griddata 对我来说效果相当不错:

>>> import numpy as np
>>> from scipy.interpolate import griddata
>>>
>>> # SETUP
>>> a = np.arange(25).reshape((5, 5)).astype(float)
>>> a
array([[  0.,   1.,   2.,   3.,   4.],
       [  5.,   6.,   7.,   8.,   9.],
       [ 10.,  11.,  12.,  13.,  14.],
       [ 15.,  16.,  17.,  18.,  19.],
       [ 20.,  21.,  22.,  23.,  24.]])
>>> a[np.random.randint(2, size=(5, 5)).astype(bool)] = np.NaN
>>> a
array([[ nan,  nan,  nan,   3.,   4.],
       [ nan,   6.,   7.,  nan,  nan],
       [ 10.,  nan,  nan,  13.,  nan],
       [ 15.,  16.,  17.,  nan,  19.],
       [ nan,  nan,  22.,  23.,  nan]])
>>>
>>> # THE INTERPOLATION
>>> x, y = np.indices(a.shape)
>>> interp = np.array(a)
>>> interp[np.isnan(interp)] = griddata(
...     (x[~np.isnan(a)], y[~np.isnan(a)]), # points we know
...     a[~np.isnan(a)],                    # values we know
...     (x[np.isnan(a)], y[np.isnan(a)]))   # points to interpolate
>>> interp
array([[ nan,  nan,  nan,   3.,   4.],
       [ nan,   6.,   7.,   8.,   9.],
       [ 10.,  11.,  12.,  13.,  14.],
       [ 15.,  16.,  17.,  18.,  19.],
       [ nan,  nan,  22.,  23.,  nan]])

我正在对3D图像进行操作,处理的是2D切片(共有4000个尺寸为350x350的切片)。整个操作仍需要大约一个小时 :/


1
感谢提供简单而紧凑的解决方案!由于griddata没有利用网格属性,所以需要花费很长时间。 - Markus Dutschke
这是一个很棒的解决方案(尽管确实有些冗长),谢谢! - Laurent

6
可能更容易的方法是在首次生成数据时改变方式,但如果不行:
bad_indexes = np.isnan(data)

创建一个布尔型数组,指示NaN的位置。
good_indexes = np.logical_not(bad_indexes)

创建一个布尔数组来指示好值所在的位置。
good_data = data[good_indexes]

这是原始数据的受限版本,不包含NaN值。

interpolated = np.interp(bad_indexes.nonzero(), good_indexes.nonzero(), good_data)

将所有错误的索引通过插值运算处理

data[bad_indexes] = interpolated

使用插值值替换原始数据。


这对我不起作用。我在interp调用中得到了“ValueError:使用序列设置数组元素”。 - Petter
2
@Ben,抱歉,我现在无法测试它。尝试在两个nonzero()后面添加[0]。 - Winston Ewert

6
或者基于温斯顿的回答进行扩展。
def pad(data):
    bad_indexes = np.isnan(data)
    good_indexes = np.logical_not(bad_indexes)
    good_data = data[good_indexes]
    interpolated = np.interp(bad_indexes.nonzero()[0], good_indexes.nonzero()[0], good_data)
    data[bad_indexes] = interpolated
    return data

A = np.array([[1, 20, 300],
              [nan, nan, nan],
              [3, 40, 500]])

A = np.apply_along_axis(pad, 0, A)
print A

结果

[[   1.   20.  300.]
 [   2.   30.  400.]
 [   3.   40.  500.]]

这很不错,但如果有多个值由于某种原因缺失,它就无法工作。 - mishaF

5

我使用插值方法替换所有NaN(Not a Number)的值。

A = np.array([1, nan, nan, 2, 2, nan, 0])
np.interp(np.arange(len(A)), 
          np.arange(len(A))[np.isnan(A) == False], 
          A[np.isnan(A) == False])

输出:

array([1. , 1.33333333, 1.66666667, 2. , 2. , 1. , 0. ])

4

基于BRYAN WOODS的回应,稍作优化后的版本。它可以正确处理源数据的起始和结束值,并且比原始版本快25-30%。此外,您可以使用不同类型的插值(有关详细信息,请参见scipy.interpolate.interp1d文档)。

import numpy as np
from scipy.interpolate import interp1d

def fill_nans_scipy1(padata, pkind='linear'):
"""
Interpolates data to fill nan values

Parameters:
    padata : nd array 
        source data with np.NaN values
    
Returns:
    nd array 
        resulting data with interpolated values instead of nans
"""
aindexes = np.arange(padata.shape[0])
agood_indexes, = np.where(np.isfinite(padata))
f = interp1d(agood_indexes
           , padata[agood_indexes]
           , bounds_error=False
           , copy=False
           , fill_value="extrapolate"
           , kind=pkind)
return f(aindexes)

In [17]: adata = np.array([1, 2, np.NaN, 4])
Out[18]: array([ 1.,  2., nan,  4.])
In [19]: fill_nans_scipy1(adata)
Out[19]: array([1., 2., 3., 4.])

类型错误:输入类型不支持ufunc 'isfinite',并且根据强制转换规则“safe”,输入无法安全地强制转换为任何支持的类型。 - Ayan Mitra
请问您能具体说明一下吗?您想要插值什么?请参考我上面的例子。一切都按预期运行。 - Prokhozhii

4

我需要一种方法,可以在数据的开头和结尾填充NaN值,而主要答案似乎没有做到这一点。

我想出的函数使用线性回归来填充NaN值。这解决了我的问题:

import numpy as np

def linearly_interpolate_nans(y):
    # Fit a linear regression to the non-nan y values

    # Create X matrix for linreg with an intercept and an index
    X = np.vstack((np.ones(len(y)), np.arange(len(y))))
    
    # Get the non-NaN values of X and y
    X_fit = X[:, ~np.isnan(y)]
    y_fit = y[~np.isnan(y)].reshape(-1, 1)
    
    # Estimate the coefficients of the linear regression
    beta = np.linalg.lstsq(X_fit.T, y_fit)[0]
    
    # Fill in all the nan values using the predicted coefficients
    y.flat[np.isnan(y)] = np.dot(X[:, np.isnan(y)].T, beta)
    return y

这是一个示例用例:

# Make an array according to some linear function
y = np.arange(12) * 1.5 + 10.

# First and last value are NaN
y[0] = np.nan
y[-1] = np.nan

# 30% of other values are NaN
for i in range(len(y)):
    if np.random.rand() > 0.7:
        y[i] = np.nan
        
# NaN's are filled in!
print (y)
print (linearly_interpolate_nans(y))

2
Bryan Woods 的答案基础上,我修改了他的代码,使其能够将仅由 NaN 组成的列表转换为零的列表:
def fill_nan(A):
    '''
    interpolate to fill nan values
    '''
    inds = np.arange(A.shape[0])
    good = np.where(np.isfinite(A))
    if len(good[0]) == 0:
        return np.nan_to_num(A)
    f = interp1d(inds[good], A[good], bounds_error=False)
    B = np.where(np.isfinite(A), A, f(inds))
    return B

简单的加法,希望对某人有用。

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