アプローチ#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