В векторизованном виде numpy: вырезание фрагментов данных (разных размеров) условно зависит от того, что внутри фрагмента - PullRequest
1 голос
/ 15 февраля 2020

У меня есть вектор целых чисел (массив 1D numpy), который выглядит следующим образом:

8, 1, 1, 2, 8, 99, 1, 2, 1, 2, 8, 2, 2, 2, 8, 99, 99, 8, 1, 1

(В векторизации) я хочу отфильтровать все данные между 8, которые содержат хотя бы одно значение 99

Таким образом, в этом примере данные, которые я хочу вырезать, выделены жирным шрифтом:

8, 1, 1, 2, <b>8, 99, 1, 2, 1, 2</b>, 8, 2, 2, 2, <b>8, 99, 99</b>, 8, 1, 1

(т. Е. Это данные, которые находятся между 8 и содержат как минимум один 99)

Так что, если бы я создал логическую маску для обрезки этих данных, она бы выглядела так:

Data: 8, 1, 1, 2, <b>8, 99, 1, 2, 1, 2</b>, 8, 2, 2, 2, <b>8, 99, 99</b>, 8, 1, 1
Mask: T, T, T, T, <b>F,  F, F, F, F, F</b>, T, T, T, T, <b>F,  F,  F</b>, T, T, T

А данные после обрезки выглядят так:

Data(Mask) = 8, 1, 1, 2, 8, 2, 2, 2, 8, 1, 1

Я могу получить с векторизованным кодом, который может сделать это, если есть гарантированный равный интервал между 8. Вот этот код:

inputRaw = np.array([8, 2, 3, 2, 99, 2, 8, 2, 3, 2, 2, 2, 8, 2, 3, 3, 3, 3])
inputPartitioned = np.reshape(inputRaw, (3, 6))
# reshaping it into an array of the form: np.array([[8, 2, 3, 2, 99, 2], [8, 2, 3, 2, 2, 2], [8, 2, 3, 3, 3, 3]])
selectedSections = np.logical_not(np.any(inputPartitioned>8, axis=1))
outputPartitioned = inputPartitioned[selectedSections]
outputFlattened = outputPartitioned.flatten()

Еще одна вещь, которая мне нужна, - это маска или индекс, который сообщает мне (в исходных индексах) данные, которые были обрезаны. (Мне это нужно, потому что у меня есть второй массив, который я хочу отслеживать, у которого есть общие индексы с первым массивом). Я могу закодировать эту маску (предполагая равный интервал между 8-ми) следующим образом:

inputIndex = np.arange(inputRaw.size)
inputIndexPartitioned =  np.reshape(inputIndex, (3, 6))
outputPartitionedIndex = inputIndexPartitioned[selectedSections]
outputFlattenedIndex = outputPartitionedIndex.flatten()

Но я не уверен, как сделать это векторизованным способом в случае неравного расстояния между 8-ми.

Есть идеи? Эти массивы очень длинные, поэтому полезны решения, которые быстро работают для больших массивов. Кроме того, я вполне уверен, что эти «99» будут всегда сразу после 8, так что, возможно, это может быть полезной информацией при создании алгоритма.

Ответы [ 3 ]

2 голосов
/ 17 февраля 2020

Подход № 1

Вот векторизованный случай для общего c случая, когда 99's происходит между двумя 8's и всеми этими элементами между этими двумя 8's затем должны быть удалены / замаскированы -

def vectorized1_app(a):
    m1 = a==8
    m2 = a==99
    d = m1.cumsum()
    occ_mask = np.bincount(d,m2)<1
    if m1.argmax() > m2.argmax():
        occ_mask[0] = ~occ_mask[0]

    if m1[::-1].argmax() > m2[::-1].argmax():
        occ_mask[-1] = ~occ_mask[-1]
    mask = occ_mask[d]
    return mask

Подход № 2

Мы также можем использовать JIT-компиляцию numba код для заданного c случая 8's и 99's в последовательности -

from numba import njit

@njit
def numba1(a, mask_out):
    N = len(a)
    fill = False
    last8_index = 0
    for i in range(N-1):
        if a[i]==8:
            if a[i+1]==99:
                fill = True
            else:
                fill = False
            last8_index = i

        if fill:        
            mask_out[i] = False

    return mask_out, last8_index

def numba1_app(a):
    N = len(a)
    mask = np.ones(N, dtype=np.bool)
    mask, last8_index = numba1(a, mask)
    if a[-1]!=8:
        mask[last8_index:] = True
    return mask

Подход № 2-B

Некоторые предельные характеристики , ускорение путем нажатия последнего шага последней проверки элемента в numba-части, например, так: *

@njit
def numba2(a, mask_out):
    N = len(a)
    fill = False
    last8_index = 0
    for i in range(N-1):
        if a[i]==8:
            if a[i+1]==99:
                fill = True
            else:
                fill = False
            last8_index = i

        if fill:        
            mask_out[i] = False

    if a[N-1]!=8:
        for i in range(last8_index,N-1):
            mask_out[i] = True

    return mask_out

def numba2_app(a):
    return numba2(a, np.ones(len(a), dtype=np.bool))

Обратите внимание, что выходные данные из опубликованных подходов являются маской, так что маскировка входного массива с этими данными даст нам сопоставимую результаты до Data(Mask).


Особый случай: замаскировать до первого 8, оставить после последнего 8

Мы могли бы изменить приложение # 1 с двумя способами сделать это -

App # 1-Mod # 1 -

m1 = a==8
m2 = a==99
d = m1.cumsum()
occ_mask = np.bincount(d,m2)<1
occ_mask[0] = False
mask = occ_mask[d]

Если вам нужно изменить регистр так, чтобы вы sh маскировали после последнего 8 также просто сделайте: occ_mask[-1] = False.

Приложение № 1-Мод № 2 -

m1 = a==8
m2 = a==99
d = m1.cumsum().clip(min=1)
occ_mask = np.bincount(d,m2)<1
mask = occ_mask[d]

Если вам нужно изменить регистр так, чтобы вы sh маскировали после последний 8 тоже сделайте: m1c = m1.cumsum(); d = m1c.clip(min=1, max=m1c.max()-1).

1 голос
/ 18 февраля 2020

Другой numpy подход:

def pp(a):                                            
    m8 = a==8
    m99 = a==99
    m = m8|m99
    i = m.nonzero()[0]
    c8 = m8[i]
    i = i[c8]
    n8 = np.count_nonzero(c8)
    if n8 == 0:
        return np.ones(a.size,bool)
    if c8[-1]:
        d8 = np.empty(n8,bool)
        d8[-1] = False
        d8[:-1] = ~c8[1:][c8[:-1]]
    else:
        d8 = ~c8[1:][c8[:-1]]
    d8[1:]^=d8[:-1]
    m8[i] = d8
    m8[0]^=True
    return np.bitwise_xor.accumulate(m8)

Например:

a = np.array([8, 1, 1, 2, 8, 99, 1, 2, 1, 2, 8, 2, 2, 2, 8, 99, 99, 8, 1, 1])
a[pp(a)]
# array([8, 1, 1, 2, 8, 2, 2, 2, 8, 1, 1])
1 голос
/ 17 февраля 2020

Это должно сработать (я избежал «довольно уверенной» части, поэтому она будет работать, даже если 99 не сразу после 8).

import numpy as np

in_arr = np.array([8, 1, 1, 2, 8, 99, 1, 2, 1, 2, 8, 2, 2, 2, 8, 99, 99, 8, 1, 1])

mask_8 = in_arr == 8
mask_8_cumsum = np.cumsum(mask_8)
print(mask_8_cumsum)
>>> [1 1 1 1 2 2 2 2 2 2 3 3 3 3 4 4 4 5 5 5]

unique_inds = np.unique(mask_8_cumsum[in_arr == 99])
print(unique_inds)
>>> [2 4]

final_mask = ~np.isin(mask_8_cumsum, unique_inds)
final_data = in_arr[final_mask]
print(final_mask)
>>> [ True  True  True  True False False False False False False  True  True
  True  True False False False  True  True  True]

print(final_data)
>>> [8 1 1 2 8 2 2 2 8 1 1]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...