Как ускорить эту векторизованную numpy-функцию?

#python #arrays #numpy #vectorization #numba

#python #массивы #numpy #векторизация #numba

Вопрос:

В последние дни я использую @Divakars функцию ‘justify_nd’, и она работает очень хорошо. Он очень эффективен при работе с большими наборами данных. Теперь у меня другая проблема:

Иногда функцию также приходится вызывать очень часто при меньших разделениях (миллионы раз). Поэтому я подумал о добавлении @jit декоратора через numba. к сожалению numba , не поддерживается np.moveaxis() .

Итак: есть ли способ сделать это с np.transpose() помощью?

Функция @Divakars:

 def justify_nd(a, invalid_val, axis, side):    
    """
    Justify ndarray for the valid elements (that are not invalid_val).

    Parameters
    ----------
    A : ndarray
        Input array to be justified
    invalid_val : scalar
        invalid value
    axis : int
        Axis along which justification is to be made
    side : str
        Direction of justification. Must be 'front' or 'end'.
        So, with 'front', valid elements are pushed to the front and
        with 'end' valid elements are pushed to the end along specified axis.
    """
    
    pushax = lambda a: np.moveaxis(a, axis, -1)
    if invalid_val is np.nan:
        mask = ~np.isnan(a)
    else:
        mask = a!=invalid_val
    justified_mask = np.sort(mask,axis=axis)
    
    if side=='front':
        justified_mask = np.flip(justified_mask,axis=axis)
            
    out = np.full(a.shape, invalid_val)
    if (axis==-1) or (axis==a.ndim-1):
        out[justified_mask] = a[mask]
    else:
        pushax(out)[pushax(justified_mask)] = pushax(a)[pushax(mask)]
    return out
  

минимальный воспроизводимый образец:

 import numpy as np, pandas as pd, random
len_ = 6; drop_Beginning_LEN = 3  ;np.random.seed(777); seed_2 = 5

for i in range(1000):# just calling it 1000 times

   ''' generating sample data '''
   
   a = pd.DataFrame({  "A":np.random.uniform(low=0.0005, high=13.12333, size=(len_)),  "B":np.random.uniform(low=0.0005, high=133.12333,       size=(len_)), "C": np.random.uniform(low=0.0001, high=13.12333, size=(len_)), "D":np.random.uniform(low=0.000005,    high=633242.12333,     size=(len_))}, dtype=np.float64)
   for col in a.columns: # place nans randomly
       a[col].iloc[random.sample(range(0, len_-1), int(len_*0.3))] = np.nan; random.seed(seed_2 ); seed_2 =1
  
   nans = np.asarray(pd.DataFrame(np.nan, index=range(len(a)), columns=a.columns))
   nans = list((map(lambda i: nans[:i], range(0,nans.shape[0]))))
   nans    = nans[::-1] # turning it around
   a = np.asarray(a) 
   a = list((map(lambda i: a[:i], range(1, a.shape[0] 1))))
   a = np.asarray([np.concatenate((x,y)) for x,y in zip(a,nans)])   
   a = np.asarray(a[(drop_Beginning_LEN-1):])

   ''' function '''
   a = justify_generic(a, invalid_val=np.nan, axis=1, side="end")
   #print(a)

  

ожидаемый результат:

(при вызове с len_ = 6; drop_Beginning_LEN =3 ;np.random.seed(777) помощью)

 ''' 
as we see: the nans are pushed to the top, but the order of the valid values remains.
[[[           nan            nan            nan            nan]
  [           nan            nan            nan            nan]
  [2.00388024e 00 9.67793310e 01 7.73773690e 00 1.70253514e 05]
  [3.96827439e 00 1.02304892e 02 4.50583685e 00 2.36363566e 05]
  [8.14593324e-01 3.58378403e 01 1.29773491e 01 1.41167463e 05]
  [6.03516909e 00 8.57355020e 01 8.22145197e 00 1.18063308e 05]]

 [[           nan            nan            nan            nan]
  [2.00388024e 00 9.67793310e 01 7.73773690e 00 1.70253514e 05]
  [3.96827439e 00 1.02304892e 02 4.50583685e 00 2.36363566e 05]
  [8.14593324e-01 3.58378403e 01 1.29773491e 01 1.41167463e 05]
  [6.03516909e 00 8.57355020e 01 8.22145197e 00 1.18063308e 05]
  [1.09613882e 01 1.24306122e 01 8.94724630e 00 2.47374828e 05]]

 [[2.00388024e 00 9.67793310e 01 7.73773690e 00 1.70253514e 05]
  [3.96827439e 00 1.02304892e 02 4.50583685e 00 2.36363566e 05]
  [8.14593324e-01 3.58378403e 01 1.29773491e 01 1.41167463e 05]
  [6.03516909e 00 8.57355020e 01 8.22145197e 00 1.18063308e 05]
  [1.09613882e 01 1.24306122e 01 8.94724630e 00 2.47374828e 05]
  [1.21653247e 01 1.06085106e 01 7.24749319e 00 1.22318572e 05]]]
  

Важно:

Мне нужны только наборы параметров invalid_val=np.nan, axis=1, side="end")

Редактировать: я забыл поместить случайные nan в фрейм данных.-> Исправлено.

Ответ №1:

Для 3D-массива они эквивалентны:

 In [13]: a = np.arange(24).reshape(2,3,4)
In [14]: np.moveaxis(a, 1, -1).shape
Out[14]: (2, 4, 3)
In [16]: np.transpose(a, [0,2,1]).shape
  

Похоже, что если вы сделали это транспонирование заранее, так что перемещение выполняется по последней оси, а не по середине, функция может быть написана без pushax (since axis==-1 ) .

Комментарии:

1. Я только что заметил, что забыл случайным образом поместить значения np.nan a в фрейм данных. Повлияет ли это на результат?

2. Попробовал -> к сожалению, подматрицы зеркально отображаются по вертикали (слева направо): (