numpy вперед заполнить с условием - PullRequest
3 голосов
/ 21 апреля 2020

У меня есть массив numpy с такими нулями, как этот.

a = np.array([3., 0., 2., 3., 0., 3., 3., 3., 0., 3., 3., 0., 3., 0., 0., 0., 0.,
   3., 3., 0., 3., 3., 0., 3., 0., 3., 0., 0., 0., 3., 0., 3., 3., 0.,
   3., 3., 0., 0., 3., 0., 0., 0., 3., 0., 3., 3., 3., 3., 3., 3., 3.,
   3., 3., 3., 3., 3., 3., 4., 3., 0., 3., 3., 3., 3., 3., 3., 3., 0.,
   0., 0., 0., 3., 0., 0., 3., 0., 0., 0., 3., 3., 3., 3., 3., 3., 3.,
   3., 0., 3., 3., 3., 3., 3., 0., 3., 3., 3., 3., 0., 0., 0., 3., 3.,
   3., 0., 3., 3., 3., 5., 3., 3., 3., 3., 3., 3., 3., 0., 3., 0., 3.,
   3., 0., 0., 0., 3., 3., 3., 3., 0., 3., 3., 3., 3., 3., 3., 3., 3.,
   3., 3., 3., 3., 0., 3., 3., 3., 3., 3., 3., 0., 3., 3., 3., 3., 3.,
   3., 3., 3., 3., 3., 3., 3., 3., 3., 3., 3., 3., 3., 0., 3., 0., 3.,
   3., 3., 3., 3., 3., 3., 3., 3., 3., 3., 3., 3., 0., 3., 3., 3., 3.,
   3., 3., 3., 3., 3., 3., 3., 3., 0., 3., 3., 0., 0., 3., 0., 0., 3.,
   0., 3., 3., 0., 3., 3., 0., 0., 3., 3., 3., 3., 3., 3., 3., 0., 3.,
   3., 3., 3., 3.])

Мне нужно заменить нули на предыдущее значение (прямая заливка) при условии. Если число нулей между двумя ненулевыми числами равно меньше или равно 2, необходимо заполнить ноль вперед.

Например,

1) Если я рассмотрим 3., 0., 2. эти три числа, число нулей между ненулевыми числами равно 1. Это должно заполнить 3.

2) Если я посчитаю 3., 0., 0., 0., 0.,3., 3. эти числа, число нулей между 3 будет больше 2., поэтому оно останется таким, как есть.

Ответы [ 3 ]

2 голосов
/ 21 апреля 2020

В тех случаях, когда разработка чисто векторизованного подхода не кажется тривиальной (если не сказать больше в этом случае), мы можем go с numba скомпилировать ваш код до C-level. Вот один из способов использования режима nopython в numba:

import numba

@numba.njit('int64[:](int64[:],uintc)') #change accordingly
def conditional_ffill(a, w):
    c=0
    last_non_zero = a[0]
    out = np.copy(a)
    for i in range(len(a)):
        if a[i]==0:
            c+=1
        elif c>0 and c<w:
            out[i-c:i] = last_non_zero
            c=0
            last_non_zero=a[i]
    return out

Проверка тестового массива divakar:

a = np.array([2, 0, 3, 0, 0, 4, 0, 0, 0, 5, 0])

conditional_ffill(a, w=1)
# array([2, 0, 3, 0, 0, 4, 0, 0, 0, 5, 0])

conditional_ffill(a, w=2)
# array([2, 2, 3, 0, 0, 4, 0, 0, 0, 5, 0])

conditional_ffill(a, w=3)
# array([2, 2, 3, 3, 3, 4, 0, 0, 0, 5, 0])

conditional_ffill(a, w=4)
# array([2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 0])

Синхронизация в большем массиве:

a_large = np.tile(a, 10000)

%timeit ffill_windowed(a_large, 3)
# 1.39 ms ± 68.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit conditional_ffill(a_large, 3)
# 150 µs ± 862 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
1 голос
/ 21 апреля 2020

Вот один подход с этим окном прямого заполнения в качестве параметра для обработки обобщенных c случаев -

# https://stackoverflow.com/a/33893692/ @Divakar
def numpy_binary_closing(mask,W):
    # Define kernel
    K = np.ones(W)

    # Perform dilation and threshold at 1
    dil = np.convolve(mask,K)>=1

    # Perform erosion on the dilated mask array and threshold at given threshold
    dil_erd = np.convolve(dil,K)>= W
    return dil_erd[W-1:-W+1]

def ffill_windowed(a, W):
    mask = a!=0
    mask_ext = numpy_binary_closing(mask,W)

    p = mask_ext & ~mask
    idx = np.maximum.accumulate(mask*np.arange(len(mask)))
    out = a.copy()
    out[p] = out[idx[p]]
    return out

Объяснение: Первая часть хорошо выполняет операцию двоичного закрытия исследуется в области обработки изображений. Итак, в нашем случае мы начнем с маски ненулевых и близких к изображению на основе параметра окна. Мы получаем индексы во всех тех местах, где нам нужно заполнить, получая форвардные индексы, исследованные в this post. Мы вводим новые значения на основе закрытой маски, полученной ранее. Вот и все!

Примеры прогонов -

In [142]: a
Out[142]: array([2, 0, 3, 0, 0, 4, 0, 0, 0, 5, 0])

In [143]: ffill_windowed(a, W=2)
Out[143]: array([2, 2, 3, 0, 0, 4, 0, 0, 0, 5, 0])

In [144]: ffill_windowed(a, W=3)
Out[144]: array([2, 2, 3, 3, 3, 4, 0, 0, 0, 5, 0])

In [146]: ffill_windowed(a, W=4)
Out[146]: array([2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 0])
0 голосов
/ 21 апреля 2020

Я не мог представить себе векторизованный способ, поэтому я просто искал процедурный:

def ffill(arr, mx):
    """Forward fill 0 values in arr with a max of mx consecutive 0 values"""
    first = None                     # first index of a sequence of 0 to fill
    prev = None                      # previous value to use
    for i, val in enumerate(arr):
        if val == 0.:                # process a null value
            if prev is not None:
                if first is None:
                    first = i
                elif i - first >= mx:   # to much consecutive 0: give up
                    prev = None
                    first = None
        else:
            if first is not None:    # there was a sequence to fill 
                arr[first:i] = prev
                first = None
...