Numpy: нечеткий оператор «greater_than», работающий над списком значений (запрашивает советы по существующему коду)

#numpy #vectorization

Вопрос:

Я реализовал функцию numpy, которая:

  • принимает в качестве входных данных:
    • массив поплавков n (строк) x m (столбцов).
    • a threshold (плавающий)
  • для каждой строки:
    • если максимальное значение строки больше или равно threshold ,
    • если этому максимальному значению не предшествует в той же строке минимальное значение, меньшее или равное -threshold ,
    • затем эта строка помечается True (больше, чем),
    • в противном случае эта строка помечена False (не больше)
  • возвращает затем этот массив n (строк) x 1 (столбцов) логических значений

То, что я реализовал, работает (по крайней мере, на приведенном примере), но я далек от того, чтобы быть экспертом в numpy, и мне интересно, нет ли более эффективного способа справиться с этим (возможно, избежать разных transpose amp; tile , например?) Я с радостью приму любые советы о том, как сделать эту функцию более эффективной и/или читаемой.

 import numpy as np
import pandas as pd

# Test data
threshold=0.02       #2%
df = pd.DataFrame({'variation_1': [0.01, 0.02, 0.005, -0.02, -0.01, -0.01],
                   'variation_2': [-0.01, 0.08, 0.08, 0.01, -0.02, 0.01],
                   'variation_3': [0.005, -0.03, -0.03, 0.002, 0.025, -0.03],
                  })

data = df.values
 

Проверка ожидаемых результатов:

 In [75]: df
Out[75]: 
   variation_1  variation_2  variation_3   # Expecting
0        0.010        -0.01        0.005   # False (no value larger than threshold)
1        0.020         0.08       -0.030   # True (1st value equal to threshold)
2        0.005         0.08       -0.030   # True (2nd value larger than threshold)
3       -0.020         0.01        0.002   # False (no value larger than threshold)
4       -0.010        -0.02        0.025   # False (2nd value lower than -threshold)
5       -0.010         0.01       -0.030   # False (no value larger than threshold)
 

Текущая функция.

 def greater_than(data: np.ndarray, threshold: float) -> np.ndarray:
    # Step 1.
    # Filtering out from 'low_max' mask the rows which 'max' is not greater than or equal
    # to 'threshold'. 'low_max' is reshaped like input array for use in next step.
    data_max = np.amax(data, axis=1)
    low_max = np.transpose([data_max >= threshold] * data.shape[1])
    
    # Step 2.
    # Filtering values preceding max of each row
    max_idx = np.argmax(data, axis=1)                   # Get idx of max.
    max_idx = np.transpose([max_idx] * data.shape[1])   # Reshape like input array.
    # Create an array of index.
    idx_array = np.tile(np.arange(data.shape[1]), (data.shape[0],1))
    # Keep indices lower than index of max for each row, and filter out rows with
    # a max too low vs 'threshold' (from step 1).
    mask_max = (idx_array <= max_idx) amp; (low_max)
    
    # Step 3.
    # On a masked array re-using mask from step 2 to filter out unqualifying values,
    # filter out rows with a 'min' preceding the 'max' and that are lower than or
    # equal to '-threshold'. 
    data = np.ma.array(data, mask=~mask_max)
    data_min = np.amin(data, axis=1)
    mask_min = data_min > -threshold
    
    # Return 'mask_min', filling masked values with 'False'.
    return np.ma.filled(mask_min, False)
 

Результаты.

 res = greater_than(data, threshold)
In [78]:res
Out[78]: array([False,  True,  True, False, False, False])
 

Заранее спасибо за любые советы!

Ответ №1:

 lesser = data <= -threshold
greater = data >= threshold

idx_lesser = np.argmax(lesser, axis=1)
idx_greater = np.argmax(greater, axis=1)

has_lesser = np.any(lesser, axis=1)
has_greater = np.any(greater, axis=1)

outptut = has_greater * (has_lesser * (idx_lesser > idx_greater)   np.logical_not(has_lesser))
 

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

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

1. Большое спасибо @Dominik. ваше предложение очень аккуратное / читабельное, намного лучше, чем мое. Это почему-то расстраивает: я очень хорошо умею делать вещи более сложными, чем они есть на самом деле… Ах, ах, еще раз спасибо!