4

numpy 配列またはできれば pandas DataFrame (2D 配列の列に沿ったブロック、および - 次元配列の最も急速に変化するインデックスに沿ったブロック) で同じ値のブロックの開始インデックスと停止インデックスを見つけたいです。私は単一の次元でブロックを探すだけで、異なる行でナンを集約したくありません。

その質問 ( numpy 配列で条件を満たす多数の連続した値を見つける) から始めて、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)

もう 1 つの例と期待される結果:

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 操作の単一パスで開始と終了を見つける)?
  • パンダでより最適化されたソリューションはありますか? (つまり、DataFrame の値に mask/where を適用するだけとは異なるソリューション)
  • 基になる配列または DataFrame が大きすぎてメモリに収まらない場合はどうなりますか?
4

2 に答える 2

7

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

この式は、数値が null 以外の値ごとに 1 ずつ増加するブロックを表す Series を作成します。

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 で、それ以外がすべてゼロである Series を作成します。

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

必要な数を達成するために、次の方法で 2 つを組み合わせることができます。

In [32]: s.isnull().astype(int).groupby(s.notnull().astype(int).cumsum()).sum()
Out[32]:
1    2
2    1
3    1
4    4
于 2013-02-25T19:59:01.803 に答える
3

任意の次元 (ndim = 2 以上) の numpy ベースの実装の下:

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)
于 2013-03-04T11:22:24.973 に答える