【问题标题】:Replace NaN's in NumPy array with closest non-NaN value用最接近的非 NaN 值替换 NumPy 数组中的 NaN
【发布时间】:2012-03-21 04:57:57
【问题描述】:

我有一个 NumPy 数组 a,如下所示:

>>> str(a)
'[        nan         nan         nan  1.44955726  1.44628034  1.44409573\n  1.4408188   1.43657094  1.43171624  1.42649744  1.42200684  1.42117704\n  1.42040255  1.41922908         nan         nan         nan         nan\n         nan         nan]'

我想用最接近的非 NaN 值替换每个 NaN,这样一开始的所有 NaN 都设置为 1.449...,最后的所有 NaN 都设置为 1.419...

我可以看到如何针对这样的特定情况执行此操作,但我需要能够对任何长度的数组执行此操作,在数组的开头和结尾使用任意长度的 NaN(不会有NaN 在数字中间)。有什么想法吗?

我可以使用np.isnan() 轻松找到 NaN,但我不知道如何获得最接近每个 NaN 的值。

【问题讨论】:

  • 注:这与 numpy 或 NaN 无关本身;这只是一般的列表处理。
  • @JoshLee - 没错,但在 numpy 数组上使用通用列表处理技术效率非常低。在 python 中遍历一个 numpy 数组的每一项比遍历一个列表的每一项要慢得多。

标签: python arrays numpy nan


【解决方案1】:

作为替代解决方案(这也将对中间的数组NaNs 进行线性插值):

import numpy as np

# Generate data...
data = np.random.random(10)
data[:2] = np.nan
data[-1] = np.nan
data[4:6] = np.nan

print data

# Fill in NaN's...
mask = np.isnan(data)
data[mask] = np.interp(np.flatnonzero(mask), np.flatnonzero(~mask), data[~mask])

print data

这会产生:

[        nan         nan  0.31619306  0.25818765         nan         nan
  0.27410025  0.23347532  0.02418698         nan]

[ 0.31619306  0.31619306  0.31619306  0.25818765  0.26349185  0.26879605
  0.27410025  0.23347532  0.02418698  0.02418698]

【讨论】:

  • 太棒了,我有一个大的 2D 数组,中间有一些麻烦的 NaN,这可以平滑。谢谢。
  • 美丽。最佳答案在这里。
  • 这很有用,但它不执行标题中的操作:“用最接近的非 NaN 值替换 NumPy 数组中的 NaN”。
【解决方案2】:

我想用最接近的非 NaN 值替换每个 NaN...数字中间不会有 NaN

下面会做:

ind = np.where(~np.isnan(a))[0]
first, last = ind[0], ind[-1]
a[:first] = a[first]
a[last + 1:] = a[last]

这是一个直接的 numpy 解决方案,不需要 Python 循环、递归、列表推导等。

【讨论】:

  • 如果其他人也有这个想法......是的,这对于没有 NaN 的数组是安全的,因为 a[:first] 将引用一个空切片,因为 first 将为 0,而 a[last + 1:]将引用一个空切片,因为last+1 将在最后一个索引之后。分配给空切片无效。为了防止代码不必要地运行,可以简单地使用if np.any(np.isnan(a)):
  • ind = np.where(a==a)[0] 是另一种查找索引的方法,因为 nan!=nan
  • 当 NaN 位于中间时这是否成立?
  • @YonatanSimson 不,它没有,它严重失败,不知道为什么接受的解决方案
【解决方案3】:

NaNs 具有比较不同的特性,因此我们可以快速找到非 nan 元素的索引:

idx = np.nonzero(a==a)[0]

现在可以很容易地将 nans 替换为所需的值:

for i in range(0, idx[0]):
    a[i]=a[idx[0]]
for i in range(idx[-1]+1, a.size)
    a[i]=a[idx[-1]]

最后,我们可以把它放在一个函数中:

import numpy as np

def FixNaNs(arr):
    if len(arr.shape)>1:
        raise Exception("Only 1D arrays are supported.")
    idxs=np.nonzero(arr==arr)[0]

    if len(idxs)==0:
        return None

    ret=arr

    for i in range(0, idxs[0]):
        ret[i]=ret[idxs[0]]

    for i in range(idxs[-1]+1, ret.size):
        ret[i]=ret[idxs[-1]]

    return ret

编辑

哎呀,来自 C++ 我总是忘记列表范围...@aix 的解决方案比我的 C++ish 循环更优雅和高效,使用它而不是我的。

【讨论】:

  • 对于它的价值,idx = np.flatnonzero(~np.isnan(a)) 是等效的,并且更具可读性,i.m.o.
  • @JoeKington:对于任何使用 NaN 的人来说,比较一个项目与自身是非常惯用的,尽管我同意你的解决方案更清晰。
  • 真的! a == a 也比 ~np.isnan(a) 更有效(直接比较而不是相反),这是走这条路的另一个原因。
【解决方案4】:

递归解决方案!

def replace_leading_NaN(a, offset=0):
    if a[offset].isNaN():
        new_value = replace_leading_NaN(a, offset + 1)
        a[offset] = new_value
        return new_value
    else:
        return a[offset]

def replace_trailing_NaN(a, offset=-1):
    if a[offset].isNaN():
        new_value = replace_trailing_NaN(a, offset - 1)
        a[offset] = new_value
        return new_value
    else:
        return a[offset]

【讨论】:

  • 谢谢!代码并没有像写的那样立即工作,但结构很好。我通过将 if a[offset].isNaN() 更改为 if np.isnan(a[offset]): 来修改它以使用 NumPy 数组,因为 ndarray 对象没有 isNaN 方法。
【解决方案5】:

我遇到了这个问题,不得不为分散的 NaN 找到一个自定义解决方案。下面的函数将任何 NaN 替换为右侧的第一个数字出现,如果不存在,则将其替换为左侧的第一个数字出现。可以进行进一步的操作以将其替换为边界出现的平均值。

import numpy as np

Data = np.array([np.nan,1.3,np.nan,1.4,np.nan,np.nan])

nansIndx = np.where(np.isnan(Data))[0]
isanIndx = np.where(~np.isnan(Data))[0]
for nan in nansIndx:
    replacementCandidates = np.where(isanIndx>nan)[0]
    if replacementCandidates.size != 0:
        replacement = Data[isanIndx[replacementCandidates[0]]]
    else:
        replacement = Data[isanIndx[np.where(isanIndx<nan)[0][-1]]]
    Data[nan] = replacement

结果是:

>>> Data
array([ 1.3,  1.3,  1.4,  1.4,  1.4,  1.4])

【讨论】:

  • 对于大型数据集,这在转换为 Numba 函数时效果很好。正是我需要的。
【解决方案6】:

我得到了这样的东西

i = [i for i in range(len(a)) if not np.isnan(a[i])]
a = [a[i[0]] if x < i[0] else (a[i[-1]] if x > i[-1] else a[x]) for x in range(len(a))]

它有点笨拙,虽然它被分成两行,其中一个是嵌套的内联 if。

【讨论】:

    【解决方案7】:

    这是一个使用简单的 python 迭代器的解决方案。它们实际上比numpy.where 更有效,尤其是对于大数组!查看类似代码的比较here

    import numpy as np
    
    a = np.array([np.NAN, np.NAN, np.NAN, 1.44955726, 1.44628034, 1.44409573, 1.4408188, 1.43657094, 1.43171624,  1.42649744, 1.42200684, 1.42117704, 1.42040255, 1.41922908, np.NAN, np.NAN, np.NAN, np.NAN, np.NAN, np.NAN])
    
    mask = np.isfinite(a)
    
    # get first value in list
    for i in range(len(mask)):
        if mask[i]:
            first = i
            break
    
    # get last vaue in list
    for i in range(len(mask)-1, -1, -1):
        if mask[i]:
            last = i
            break
    
    # fill NaN with near known value on the edges
    a = np.copy(a)
    a[:first] = a[first]
    a[last + 1:] = a[last]
    
    print(a)
    

    输出:

    [1.44955726 1.44955726 1.44955726 1.44955726 1.44628034 1.44409573
     1.4408188  1.43657094 1.43171624 1.42649744 1.42200684 1.42117704
     1.42040255 1.41922908 1.41922908 1.41922908 1.41922908 1.41922908
     1.41922908 1.41922908]
    

    它仅替换此处请求的第一个和最后一个 NaN。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-03-25
      • 2017-12-23
      • 2017-08-14
      • 1970-01-01
      • 2013-06-18
      • 1970-01-01
      • 2013-09-12
      • 2017-08-25
      相关资源
      最近更新 更多