2

各行の nan 値の数が異なる 2 次元配列 x があります。

array([[   nan, -0.355, -0.036, ...,    nan,    nan],
       [   nan, -0.341, -0.047, ...,    nan,  0.654],
       [  .016, -1.147, -0.667, ...,    nan,    nan],
       ..., 
       [   nan,  0.294, -0.235, ...,    0.65,   nan]])

この配列を使用して、行ごとに、最初の 25 パーセンタイル内のすべての値の平均を計算したいと考えています。私は次のことをしています:

limit = np.nanpercentile(x, 25, axis=1) # output 1D array
ans = np.nanmean(x * (x < limit[:,None]), axis=1)

しかし、これは間違った結果をもたらしています - 具体的には、カウント (np.nansum/np.nanmean) は、選択したパーセンタイルに関係なく同じままです。これは、比較が真でない場合にゼロを生成し、平均の有効な値としてカウントされるためです。x[x>limit[:,None]]1D配列が得られ、2D結果が必要なため、単純に使用できません。

私は次のようにして解決しました:

f = x.copy()
f[f > limit[:,None]] = np.nan
ans = np.nanmean(f, axis=1) 

これに対するより良いアプローチはありますか?

4

1 に答える 1

2

アプローチ#1:NaNs元の配列からの無効なマスクとからのマスクを作成できますf > limit[:,None]。次に、このマスクを使用np.nanmeanして、有効なもののみを考慮して同等のアプローチを実行しmaskingます。masks/boolean arraysフローティング pt 配列よりも 8 分の 1 のメモリを占有するため、使用する利点はメモリの点です。したがって、次のような実装があります-

# Create mask of non-NaNs and thresholded ones
mask = ~np.isnan(x) & (x <= limit[:,None])

# Get the row, col indices. Use the row indices for bin-based summing and
# finally averaging by using those indices to get the group lengths.
r,c = np.where(mask)
out = np.bincount(r,x[mask])/np.bincount(r)

アプローチ #2 :np.add.reduceatマスキングに従ってビンが既にソートされているため、ここで役立つ which を使用することもできます。したがって、もう少し効率的なのは次のようになります-

# Get the valid mask as before
mask = ~np.isnan(x) & (x <= limit[:,None])

# Get valid row count. Use np.add.reduceat to perform grouped summations
# at intervals separated by row indices.
rowc = mask.sum(1)
out = np.add.reduceat(x[mask],np.append(0,rowc[:-1].cumsum()))/rowc

ベンチマーク

関数定義 -

def original_app(x, limit):
    f = x.copy()
    f[f > limit[:,None]] = np.nan
    ans = np.nanmean(f, axis=1) 
    return ans

def proposed1_app(x, limit):
    mask = ~np.isnan(x) & (x <= limit[:,None])
    r,c = np.where(mask)
    out = np.bincount(r,x[mask])/np.bincount(r)
    return out

def proposed2_app(x, limit):
    mask = ~np.isnan(x) & (x <= limit[:,None])
    rowc = mask.sum(1)
    out = np.add.reduceat(x[mask],np.append(0,rowc[:-1].cumsum()))/rowc
    return out

タイミングと検証 -

In [402]: # Setup inputs
     ...: x = np.random.randn(400,500)
     ...: x.ravel()[np.random.randint(0,x.size,x.size//4)] = np.nan # Half as NaNs
     ...: limit = np.nanpercentile(x, 25, axis=1)
     ...: 

In [403]: np.allclose(original_app(x, limit),proposed1_app(x, limit))
Out[403]: True

In [404]: np.allclose(original_app(x, limit),proposed2_app(x, limit))
Out[404]: True

In [405]: %timeit original_app(x, limit)
100 loops, best of 3: 5 ms per loop

In [406]: %timeit proposed1_app(x, limit)
100 loops, best of 3: 4.02 ms per loop

In [407]: %timeit proposed2_app(x, limit)
100 loops, best of 3: 2.18 ms per loop
于 2016-11-21T10:11:22.733 に答える