2

У меня есть 2d массива х, причем каждый ряд имеет различное число значений NaN:означают до 25 процентилей для строк массива, содержащего нан

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) остается неизменным независимо от того, что процентиль я выбираю, потому что сравнение дает ноль, где не true, и подсчитывается как допустимое значение для среднего значения. Я не могу просто использовать x[x>limit[:,None]], потому что это дает 1D-массив, и мне нужен 2D-результат.

Я решил ее следующим образом:

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

Есть ли лучший подход к этому?

+0

ли вы имеете в виду 'предел ', где вы написали' low'? Если так, то я думаю, что это именно та процедура, которую я бы тоже принял. Какой подход лучше всего вы ищете? – Praveen

+0

спасибо, да, это предел. Желательно, чтобы подход без слишком большого количества промежуточных шагов, потому что с этим, мне нужно создать копию каждый раз, когда я хочу другой процентиль. – dayum

ответ

2

Подход № 1: Вы можете создать маску недействительных те, которые были бы NaNs из исходного массива и маски из f > limit[:,None]. Затем используйте эту маску для выполнения эквивалентного подхода np.nanmean, считая действительные только с masking. Преимущество использования masks/boolean arrays было бы в плане памяти, поскольку оно занимало бы в 8 раз меньше памяти, чем плавающие массивы pt. Таким образом, мы имели бы реализацию как так -

# 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) 

Approach # 2: Мы могли бы также использовать np.add.reduceat, которые будут полезны здесь, как бункеры уже отсортированы в соответствии с маскировкой. Таким образом, немного более эффективным будет, как так -

# 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