【问题标题】:Interpolate NaN values in a numpy array在 numpy 数组中插入 NaN 值
【发布时间】:2011-09-25 00:25:44
【问题描述】:

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

例如,

[1 1 1 nan nan 2 2 nan 0]

会被转换成

[1 1 1 1.3 1.6 2 2  1  0]

【问题讨论】:

  • 我为写给旧线程而道歉,但我认为值得混淆。更简单的方法是使用 pandas 和 numpy:pd.DataFrame([1, 3, 4, np.nan, 6]).interpolate().values.ravel().tolist()
  • 我发现pd.Series([1, 3, 4, np.nan, 6]).interpolate.get_values().tolist() 更短。
  • 从 pandas 1.2.4 开始:pd.Series([1, 3, 4, np.nan, 6]).interpolate().tolist() 更短

标签: python numpy interpolation nan


【解决方案1】:

我使用插值替换所有 NaN 值。

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. ])

【讨论】:

    【解决方案2】:

    对我来说,导入 scipy 看起来有点矫枉过正。这是使用 numpy 并保持与 np.interp 相同约定的简单方法

       def interp_nans(x:[float],left=None, right=None, period=None)->[float]:
        """ 
          e.g. [1 1 1 nan nan 2 2 nan 0] -> [1 1 1 1.3 1.6 2 2  1  0]
        
        """
        xp = [i for i, yi in enumerate(x) if np.isfinite(yi)]
        fp = [yi for i, yi in enumerate(x) if np.isfinite(yi)]
        return list(np.interp(x=list(range(len(x))), xp=xp, fp=fp,left=left,right=right,period=period))
    

    【讨论】:

      【解决方案3】:

      正如之前的评论所建议的,最好的方法是使用同行评审的实现。 pandas库有一个一维数据的插值方法,在SeriesDataFrame中插值np.nan值:

      pandas.Series.interpolatepandas.DataFrame.interpolate

      文档非常简洁,推荐通读!我的实现:

      import pandas as pd
      
      magnitudes_series = pd.Series(magnitudes)    # Convert np.array to pd.Series
      magnitudes_series.interpolate(
          # I used "akima" because the second derivative of my data has frequent drops to 0
          method=interpolation_method,
      
          # Interpolate from both sides of the sequence, up to you (made sense for my data)
          limit_direction="both",
      
          # Interpolate only np.nan sequences that have number sequences at the ends of the respective np.nan sequences
          limit_area="inside",
      
          inplace=True,
      )
      
      # I chose to remove np.nan at the tails of data sequence
      magnitudes_series.dropna(inplace=True)
      
      result_in_numpy_array = magnitudes_series.values
      

      【讨论】:

        【解决方案4】:

        使用填充关键字进行插值和外插

        如果两边都存在有限值,则以下解决方案通过np.interp 对数组中的 nan 值进行插值边界处的Nan值np.pad处理,使用constantreflect等模式。

            import numpy as np
            import matplotlib.pyplot as plt
            
            
            def extrainterpolate_nans_1d(
                    arr, kws_pad=({'mode': 'edge'}, {'mode': 'edge'})
                    ):
                """Interpolates and extrapolates nan values.
            
                Interpolation is linear, compare np.interp(..).
                Extrapolation works with pad keywords, compare np.pad(..).
            
                Parameters
                ----------
                arr : np.ndarray, shape (N,)
                    Array to replace nans in.
                kws_pad : dict or (dict, dict)
                    kwargs for np.pad on left and right side
            
                Returns
                -------
                bool
                    Description of return value
            
                See Also
                --------
                https://numpy.org/doc/stable/reference/generated/numpy.interp.html
                https://numpy.org/doc/stable/reference/generated/numpy.pad.html
                https://stackoverflow.com/a/43821453/7128154
                """
                assert arr.ndim == 1
                if isinstance(kws_pad, dict):
                    kws_pad_left = kws_pad
                    kws_pad_right = kws_pad
                else:
                    assert len(kws_pad) == 2
                    assert isinstance(kws_pad[0], dict)
                    assert isinstance(kws_pad[1], dict)
                    kws_pad_left = kws_pad[0]
                    kws_pad_right = kws_pad[1]
            
                arr_ip = arr.copy()
            
                # interpolation
                inds = np.arange(len(arr_ip))
                nan_msk = np.isnan(arr_ip)
                arr_ip[nan_msk] = np.interp(inds[nan_msk], inds[~nan_msk], arr[~nan_msk])
            
                # detemine pad range
                i0 = next(
                    (ids for ids, val in np.ndenumerate(arr) if not np.isnan(val)), 0)[0]
                i1 = next(
                    (ids for ids, val in np.ndenumerate(arr[::-1]) if not np.isnan(val)), 0)[0]
                i1 = len(arr) - i1
                # print('pad in range [0:{:}] and [{:}:{:}]'.format(i0, i1, len(arr)))
            
                # pad
                arr_pad = np.pad(
                    arr_ip[i0:], pad_width=[(i0, 0)], **kws_pad_left)
                arr_pad = np.pad(
                    arr_pad[:i1], pad_width=[(0, len(arr) - i1)], **kws_pad_right)
            
                return arr_pad
            
            
            # setup data
            ys = np.arange(30, dtype=float)**2/20
            ys[:5] = np.nan
            ys[20:] = 20
            ys[28:] = np.nan
            ys[[7, 13, 14, 18, 22]] = np.nan
            
            
            ys_ie0 = extrainterpolate_nans_1d(ys)
            kws_pad_sym = {'mode': 'symmetric'}
            kws_pad_const7 = {'mode': 'constant', 'constant_values':7.}
            ys_ie1 = extrainterpolate_nans_1d(ys, kws_pad=(kws_pad_sym, kws_pad_const7))
            ys_ie2 = extrainterpolate_nans_1d(ys, kws_pad=(kws_pad_const7, kws_pad_sym))
            
            fig, ax = plt.subplots()
            
            
            ax.scatter(np.arange(len(ys)), ys, s=15**2, label='ys')
            ax.scatter(np.arange(len(ys)), ys_ie0, s=8**2, label='ys_ie0, left_pad edge, right_pad edge')
            ax.scatter(np.arange(len(ys)), ys_ie1, s=6**2, label='ys_ie1, left_pad symmetric, right_pad 7')
            ax.scatter(np.arange(len(ys)), ys_ie2, s=4**2, label='ys_ie2, left_pad 7, right_pad symmetric')
            ax.legend()
        

        【讨论】:

          【解决方案5】:

          根据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.])
          

          【讨论】:

          • TypeError: 输入类型不支持 ufunc 'isfinite',根据转换规则 ''safe'' 无法安全地将输入强制转换为任何支持的类型
          • 您能说得更具体些吗?你想插值什么?请看我上面的例子。一切都按预期工作。
          【解决方案6】:

          我需要一种方法,该方法还可以在数据结尾的开头填充 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))
          

          【讨论】:

            【解决方案7】:

            只需使用 numpy logical and there 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
            

            【讨论】:

            • 这不处理序列开头或结尾的 NaN。
            【解决方案8】:

            基于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
            

            简单的补充,希望对大家有用。

            【讨论】:

              【解决方案9】:

              对于二维数据,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 切片(350x350 的 4000 个切片)进行操作。整个操作仍然需要一个小时左右:/

              【讨论】:

              • 感谢简单紧凑的解决方案!这需要很长时间,因为具有讽刺意味的是 griddata 并没有利用 grid 属性。
              • 这是一个很好的解决方案(虽然确实很长),谢谢!
              【解决方案10】:

              让我们首先定义一个简单的辅助函数,以便更直接地处理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 处理,但类似的技术也用于各种其他上下文。

              【讨论】:

                【解决方案11】:

                或者以温斯顿的回答为基础

                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.]]
                

                【讨论】:

                • 这很好,但如果由于某种原因缺少一个以上的值,它就不起作用。
                【解决方案12】:

                我想出了这个代码:

                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.        ]
                

                【讨论】:

                • @fmonegaglia,不幸的是,这个脚本只在二维数组的一个轴上进行插值,它不是二维插值。在二维数组中对 NaN 进行插值的需要有一个 scipy 问题:github.com/scipy/scipy/issues/1682
                • 从引用的 issue 中,您或许可以直接使用 astropy 的 convolve 函数。
                • 将 - 替换为 ~ 以使其工作(可能版本会随着时间而变化)
                【解决方案13】:

                首先更改数据的生成方式可能更容易,但如果不是:

                bad_indexes = np.isnan(data)
                

                创建一个布尔数组,指示 nans 的位置

                good_indexes = np.logical_not(bad_indexes)
                

                创建一个布尔数组,指示好值区域的位置

                good_data = data[good_indexes]
                

                原始数据的限制版本,不包括 nans

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

                通过插值运行所有坏索引

                data[bad_indexes] = interpolated
                

                用插值替换原始数据。

                【讨论】:

                • 这对我不起作用。我收到 ValueError: setting an array element with a sequence. 的 interp 电话
                • @Ben,对不起,我现在不能/不能测试它。尝试在两个 nonzero() 之后添加 [0]。
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2023-04-03
                • 2021-12-17
                • 1970-01-01
                • 2020-07-02
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多