Numpy эффективно заменяет массив 2d bool суммой последовательных элементов по оси

У меня есть логический массив (bool_arr), в котором я хочу заменить последовательные ненулевые числа в столбцах их количеством (consecutive_count) (которое также является максимальным/последним числом последовательной группы)

bool_arr =            consecutive_count = 
[[1 1 1 1 0 1]        [[3 6 1 6 0 1]
 [1 1 0 1 1 0]         [3 6 0 6 5 0]
 [1 1 1 1 1 1]         [3 6 3 6 5 2]
 [0 1 1 1 1 1]         [0 6 3 6 5 2]
 [1 1 1 1 1 0]         [2 6 3 6 5 0]
 [1 1 0 1 1 1]]        [2 6 0 6 5 1]]

Я создал свою собственную функцию, которая получает кумулятивную сумму последовательных ненулевых элементов по столбцам.

consecutive_cumsum = 
[[1 1 1 1 0 1]
 [2 2 0 2 1 0]
 [3 3 1 3 2 1]
 [0 4 2 4 3 2]
 [1 5 3 5 4 0]
 [2 6 0 6 5 1]]

В настоящее время я использую следующее, чтобы получить consecutive_count:

bool_arr = np.array([[1,1,1,1,0,1],
                     [1,1,0,1,1,0],
                     [1,1,1,1,1,1],
                     [0,1,1,1,1,1],
                     [1,1,1,1,1,0],
                     [1,1,0,1,1,1]])

consecutive_cumsum = np.array([[1,1,1,1,0,1],
                               [2,2,0,2,1,0],
                               [3,3,1,3,2,1],
                               [0,4,2,4,3,2],
                               [1,5,3,5,4,0],
                               [2,6,0,6,5,1]])

consecutive_count = consecutive_cumsum.copy()
for x in range(consecutive_count.shape[1]):
    maximum = 0
    for y in range(consecutive_count.shape[0]-1, -1, -1):
        if consecutive_cumsum[y,x] > 0:
            if consecutive_cumsum[y,x] < maximum: consecutive_count[y,x] = maximum
            else: maximum = consecutive_cumsum[y,x]
        else: maximum = 0

print(consecutive_count)

Он отлично работает, но я перебираю каждый элемент, чтобы заменить его максимальным значением между нулями.

Есть ли способ использовать numpy для векторизации этого вместо того, чтобы перебирать все элементы. И в качестве бонуса укажите, на какой оси (строка или столбец) он будет это выполнять.

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
4
0
271
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Использование itertools.groupby:

import itertools

for i in range(b.shape[1]):
    counts = []
    for k,v in itertools.groupby(b[:,i]):
        g = list(v)
        counts.extend([sum(g)] * len(g))    
    b[:,i] = counts   

Выход:

array([[3, 6, 1, 6, 0, 1],
       [3, 6, 0, 6, 5, 0],
       [3, 6, 3, 6, 5, 2],
       [0, 6, 3, 6, 5, 2],
       [2, 6, 3, 6, 5, 0],
       [2, 6, 0, 6, 5, 1]])
Ответ принят как подходящий

Новые (вероятно, v1.15.0) append и prepend ключевые слова np.diff упрощают эту задачу:

bnd = np.diff(bool_arr, axis=0, prepend=0, append=0)
x, y = np.where(bnd.T)
bnd.T[x, y] *= (y[1::2]-y[::2]).repeat(2)
bnd[:-1].cumsum(axis=0)
# array([[3, 6, 1, 6, 0, 1],
#        [3, 6, 0, 6, 5, 0],
#        [3, 6, 3, 6, 5, 2],
#        [0, 6, 3, 6, 5, 2],
#        [2, 6, 3, 6, 5, 0],
#        [2, 6, 0, 6, 5, 1]])

С выбираемой осью:

def count_ones(a, axis=-1):
    a = a.swapaxes(-1, axis)
    bnd = np.diff(a, axis=-1, prepend=0, append=0)
    *idx, last = np.where(bnd)
    bnd[(*idx, last)] *= (last[1::2]-last[::2]).repeat(2)
    return bnd[..., :-1].cumsum(axis=-1).swapaxes(-1, axis)

ОБНОВЛЕНИЕ: и версия, которая работает с общими (а не только 0/1) записями:

def sum_stretches(a, axis=-1):
    a = a.swapaxes(-1, axis)
    dtype = np.result_type(a, 'i1')
    bnd = np.diff((a!=0).astype(dtype), axis=-1, prepend=0, append=0)
    *idx, last = np.where(bnd)
    A = np.concatenate([np.zeros((*a.shape[:-1], 1), a.dtype), a.cumsum(axis=-1)], -1)[(*idx, last)]
    bnd[(*idx, last)] *= (A[1::2]-A[::2]).repeat(2)
    return bnd[..., :-1].cumsum(axis=-1).swapaxes(-1, axis)

У меня были проблемы с переполнением при настройке .astype(a.dtype) для bnd и A для np.zeros(--- , a.dtype), когда a.dtype = 'uint8' (opencv). Это работает, если вы измените a.dtype на 'int32'

Ta946 09.04.2019 16:30

@ Ta946 Обновил ответ. Надеюсь, это исправит.

Paul Panzer 09.04.2019 17:06

опираясь на ответ полпанцер для бедных душ (таких как я), у которых нет numpy v1.15+

def sum_stretches(a, axis=-1):
    a = a.swapaxes(-1, axis)
    padding = [[0,0].copy()]*a.ndim
    padding[-1] = [1,1]
    padded = np.pad((a!=0), padding, 'constant', constant_values=0).astype('int32')
    bnd = np.diff(padded, axis=-1)
    *idx, last = np.where(bnd)
    A = np.concatenate([np.zeros((*a.shape[:-1], 1), 'int32'), a.cumsum(axis=-1)], -1)[(*idx, last)]
    bnd[(*idx, last)] *= (A[1::2]-A[::2]).repeat(2)
    return bnd[..., :-1].cumsum(axis=-1).swapaxes(-1, axis)

Другие вопросы по теме