在Python/Numpy/Pandas中查找连续值块的开始和停止

在Python/Numpy/Pandas中查找连续值块的开始和停止,python,numpy,pandas,Python,Numpy,Pandas,我想在numpy数组或最好是pandas数据帧中找到相同值块的开始和停止索引(对于二维数组,沿着列的块,对于n维数组,沿着变化最快的索引)。我只在一个维度上查找块,不想在不同的行上忽略NaN 从这个问题()开始,我编写了以下为2D数组查找np.nan的解决方案: import numpy as np a = np.array([ [1, np.nan, np.nan, 2], [np.nan, 1, np.nan, 3], [np.nan, np

我想在numpy数组或最好是pandas数据帧中找到相同值块的开始和停止索引(对于二维数组,沿着列的块,对于n维数组,沿着变化最快的索引)。我只在一个维度上查找块,不想在不同的行上忽略NaN

从这个问题()开始,我编写了以下为2D数组查找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的解决方案)
  • 当底层阵列或数据帧太大而无法放入内存时会发生什么情况

我将您的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
此表达式创建一个序列,其中每个nan都是1,其他所有内容都是零:

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

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

以便:

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)
以及:


哇,这是一些我一直印象深刻的熊猫魔术!但是,您的实现考虑到连续的NANS,但不同的列/行实际上属于相同的“块”。我创建了一个小ipython笔记本()来演示这个问题。就性能而言,numpy实现的速度也快了大约3倍。。。实际上,对于ndim=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)