【问题标题】:Finding start and stops of consecutive values block in Python/Numpy/Pandas在 Python/Numpy/Pandas 中查找连续值块的开始和停止
【发布时间】:2013-02-10 07:34:13
【问题描述】:

我想在 numpy 数组或最好是 pandas DataFrame 中找到相同值块的开始和停止索引(二维数组沿列的块,以及 n 维数组沿最快变化的索引的块)。我只在单个维度上查找块,不想在不同的行上聚合 nans。

从那个问题 (Find large number of consecutive values fulfilling condition in a numpy array) 开始,我编写了以下解决方案,为二维数组找到 np.nan:

import numpy as np
a = np.array([
        [1, np.nan, np.nan, 2],
        [np.nan, 1, np.nan, 3], 
        [np.nan, np.nan, np.nan, np.nan]
    ])

nan_mask = np.isnan(a)
start_nans_mask = np.hstack((np.resize(nan_mask[:,0],(a.shape[0],1)),
                             np.logical_and(np.logical_not(nan_mask[:,:-1]), nan_mask[:,1:])
                             ))
stop_nans_mask = np.hstack((np.logical_and(nan_mask[:,:-1], np.logical_not(nan_mask[:,1:])),
                            np.resize(nan_mask[:,-1], (a.shape[0],1))
                            ))

start_row_idx,start_col_idx = np.where(start_nans_mask)
stop_row_idx,stop_col_idx = np.where(stop_nans_mask)

这让我可以在应用 pd.fillna 之前分析缺失值补丁的长度分布。

stop_col_idx - start_col_idx + 1
array([2, 1, 1, 4], dtype=int64)

再举一个例子和预期的结果:

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

array([2, 1, 2, 4], dtype=int64)

而不是

array([2, 1, 6], dtype=int64)

我的问题如下:

  • 有没有办法优化我的解决方案(在单遍掩码/where 操作中查找开始和结束)?
  • pandas 中是否有更优化的解决方案? (即,与仅在 DataFrame 的值上应用掩码/位置不同的解决方案)
  • 当底层数组或 DataFrame 太大而无法放入内存时会发生什么?

【问题讨论】:

    标签: python numpy pandas


    【解决方案1】:

    我将您的 np.array 加载到数据框中:

    In [26]: df
    Out[26]:
        0   1   2   3
    0   1 NaN NaN   2
    1 NaN   1 NaN   2
    2 NaN NaN NaN NaN
    

    然后转置并变成一个系列。我认为这类似于np.hstack

    In [28]: s = df.T.unstack(); s
    Out[28]:
    0  0     1
       1   NaN
       2   NaN
       3     2
    1  0   NaN
       1     1
       2   NaN
       3     2
    2  0   NaN
       1   NaN
       2   NaN
       3   NaN
    

    这个表达式创建了一个系列,其中的数字代表每个非空值加 1 的块:

    In [29]: s.notnull().astype(int).cumsum()
    Out[29]:
    0  0    1
       1    1
       2    1
       3    2
    1  0    2
       1    3
       2    3
       3    4
    2  0    4
       1    4
       2    4
       3    4
    

    这个表达式创建了一个 Series,其中每个 nan 都是 1,其他的都是 0:

    In [31]: s.isnull().astype(int)
    Out[31]:
    0  0    0
       1    1
       2    1
       3    0
    1  0    1
       1    0
       2    1
       3    0
    2  0    1
       1    1
       2    1
       3    1
    

    我们可以通过以下方式将两者结合起来以达到您需要的计数:

    In [32]: s.isnull().astype(int).groupby(s.notnull().astype(int).cumsum()).sum()
    Out[32]:
    1    2
    2    1
    3    1
    4    4
    

    【讨论】:

    • 哇,这就是我一直印象深刻的熊猫魔法!但是,您的实现认为连续的 nan 但在不同的列/行上实际上属于同一个“块”。我创建了一个小型 ipython 笔记本 (nbviewer.ipython.org/url/www.guillaumeallain.info/…) 来显示该问题。在性能方面,numpy 的实现也差不多。快 3 倍。
    【解决方案2】:

    低于任何维度的基于 numpy 的实现(ndim = 2 或更多):

    def get_nans_blocks_length(a):
        """
        Returns 1D length of np.nan s block in sequence depth wise (last axis).
        """
        nan_mask = np.isnan(a)
        start_nans_mask = np.concatenate((np.resize(nan_mask[...,0],a.shape[:-1]+(1,)),
                                     np.logical_and(np.logical_not(nan_mask[...,:-1]), nan_mask[...,1:])
                                     ), axis=a.ndim-1)
        stop_nans_mask = np.concatenate((np.logical_and(nan_mask[...,:-1], np.logical_not(nan_mask[...,1:])),
                                    np.resize(nan_mask[...,-1], a.shape[:-1]+(1,))
                                    ), axis=a.ndim-1)
    
        start_idxs = np.where(start_nans_mask)
        stop_idxs = np.where(stop_nans_mask)
        return stop_idxs[-1] - start_idxs[-1] + 1
    

    这样:

    a = np.array([
            [1, np.nan, np.nan, np.nan],
            [np.nan, 1, np.nan, 2], 
            [np.nan, np.nan, np.nan, np.nan]
        ])
    get_nans_blocks_length(a)
    array([3, 1, 1, 4], dtype=int64)
    

    还有:

    a = np.array([
            [[1, np.nan], [np.nan, np.nan]],
            [[np.nan, 1], [np.nan, 2]], 
            [[np.nan, np.nan], [np.nan, np.nan]]
        ])
    get_nans_blocks_length(a)
    array([1, 2, 1, 1, 2, 2], dtype=int64)
    

    【讨论】:

    • 漂亮的小sn-p...实际上对于 ndim = 1 来说这也不应该太离谱。
    猜你喜欢
    • 2016-03-06
    • 1970-01-01
    • 1970-01-01
    • 2018-02-15
    • 1970-01-01
    • 2020-06-20
    • 1970-01-01
    • 2021-09-04
    • 1970-01-01
    相关资源
    最近更新 更多