проверить, имеет ли массив numpy 0 на всех своих границах [закрыто]

13

Какой самый быстрый способ проверить, имеет ли многомерный массив с нулевыми значениями 0 со всех сторон?

Итак, для простого примера 2D, у меня есть:

x = np.random.rand(5, 5)
assert np.sum(x[0:,  0]) == 0
assert np.sum(x[0,  0:]) == 0
assert np.sum(x[0:, -1]) == 0
assert np.sum(x[-1, 0:]) == 0

Хотя для 2D-случаев это нормально, писать для более высоких измерений немного утомительно, и мне было интересно, есть ли какой-нибудь умный хитрый прием, который я могу использовать здесь, чтобы сделать его эффективным и более удобным в обслуживании.

Лука
источник
8
Не np.all (x[:, 0] == 0)будет безопаснее, чем сумма? Проверка суммы верна, только если все числа положительны.
Деми-Луна
2
Связанный: Как получить все края массива?
Георгий
1
@ Деми-Люм имеет смысл. В моем случае все будет> = 0, но ваш комментарий приветствуется :)
Лука
1
В трехмерном случае вы имеете в виду грани (их шесть) или ребра (их 12) куба?
Риккардо Букко
@RiccardoBucco Да, 6 лиц. но моя проблема в том, что он может пойти в более высоком измерении, чем 3.
Лука

Ответы:

7

Вот как вы можете это сделать:

assert(all(np.all(np.take(x, index, axis=axis) == 0)
           for axis in range(x.ndim)
           for index in (0, -1)))

np.take делает то же самое, что и "модная" индексация.

Риккардо Букко
источник
1
@Luca: документация не проясняет, но numpy.takeделает копию. Это может привести к тому, что он будет работать хуже, чем код, основанный на представлении. (Время было бы необходимо, чтобы быть уверенным - эффективность представления NumPy иногда
странна
1
@RiccardoBucco: len(x.shape)можно написать проще x.ndim.
user2357112 поддерживает Монику
1
@ user2357112supportsMonica спасибо, я исправил это :)
Riccardo Bucco
5
Кроме того, использование понимания списка предотвращает allкороткое замыкание. Вы можете снять скобки, чтобы использовать выражение генератора, позволяющее allвернуться, как только один numpy.allвызов вернется False.
user2357112 поддерживает Монику
1
@ user2357112supportsMonica Правда !!
Риккардо Букко
5

Вот ответ, который на самом деле исследует части интересующего вас массива и не тратит время на создание маски размером всего массива. Есть цикл уровня Python, но он короткий, с итерациями, пропорциональными количеству измерений, а не размеру массива.

def all_borders_zero(array):
    if not array.ndim:
        raise ValueError("0-dimensional arrays not supported")
    for dim in range(array.ndim):
        view = numpy.moveaxis(array, dim, 0)
        if not (view[0] == 0).all():
            return False
        if not (view[-1] == 0).all():
            return False
    return True
user2357112 поддерживает Monica
источник
Есть ли обстоятельства, которые not (view[0] == 0).all()не эквивалентны view[0].any()?
Пол Панцер
@PaulPanzer: Я полагаю, view[0].any()будет работать тоже. Я не совсем уверен в последствиях приведения типов и буферизации к двум вариантам - view[0].any()теоретически они могут быть реализованы быстрее, но раньше я видел странные результаты и не до конца понимаю, что такое буферизация.
user2357112 поддерживает Monica
Я полагаю, view[0].view(bool).any()было бы скоростное решение.
Пол Панцер
@PaulPanzer: argmaxможет фактически превзойти anyлогическое представление . Этот материал становится странным.
user2357112 поддерживает Monica
(Кроме того , независимо от того , argmaxили any, используя логические средства обработки вида отрицательный нуля , как неравномерные к обычному нулю.)
user2357112 поддерживает Монику
2

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

x = np.array(
        [
            [
                [0 , 1, 1, 0],
                [0 , 2, 3, 0],
                [0 , 4, 5, 0]
            ],
            [
                [0 , 6, 7, 0],
                [0 , 7, 8, 0],
                [0 , 9, 5, 0]
            ]
        ])

xx = np.array(
        [
            [
                [0 , 0, 0, 0],
                [0 , 2, 3, 0],
                [0 , 0, 0, 0]
            ],
            [
                [0 , 0, 0, 0],
                [0 , 7, 8, 0],
                [0 , 0, 0, 0]
            ]
        ])

def check_edges(x):

    idx = x.shape
    chunk = np.prod(idx[:-2])
    x = x.reshape((chunk*idx[-2], idx[-1]))
    for block in range(chunk):
        z = x[block*idx[-2]:(block+1)*idx[-2], :]
        if not np.all(z[:, 0] == 0):
            return False
        if not np.all(z[:, -1] == 0):
            return False
        if not np.all(z[0, :] == 0):
            return False
        if not np.all(z[-1, :] == 0):
            return False

    return True

Который будет производить

>>> False
>>> True

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

lwileczek
источник
Это исследует неправильные части массива. Для 3-мерного массива мы хотим исследовать грани всего массива, а не ребра каждого 2-мерного подмассива.
user2357112 поддерживает Monica
Ах, это имеет больше смысла. Я неправильно понял
lwileczek
1

возможно, вы ищете оператор многоточия, который будет работать для многих измерений:

import numpy as np

# data
x = np.random.rand(2, 5, 5)
x[..., 0:, 0] = 0
x[..., 0, 0:] = 0
x[..., 0:, -1] = 0
x[..., -1, 0:] = 0

test = np.all(
    [
        np.all(x[..., 0:, 0] == 0),
        np.all(x[..., 0, 0:] == 0),
        np.all(x[..., 0:, -1] == 0),
        np.all(x[..., -1, 0:] == 0),
    ]
)

print(test)
daveg
источник
Это не будет окрашивать все лица. Например, попробуйте это с (4, 4, 4) кубом.
Лука
Я не уверен, что вы подразумеваете под окраской граней, но это сработает, если вы сделаете x (4, 4, 4)
daveg
1

Вы можете использовать sliceи логическое маскирование для выполнения работы:

def get_borders(arr):
    s=tuple(slice(1,i-1) for i in a.shape)
    mask = np.ones(arr.shape, dtype=bool)
    mask[s] = False
    return(arr[mask])

Эта функция сначала формирует «ядро» массива в кортеж s, а затем создает маску, которая отображается Trueтолько для граничных точек. Затем логическое индексирование доставляет граничные точки.

Рабочий пример:

a = np.arange(16).reshape((4,4))

print(a)
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])

borders = get_borders(a)
print(borders)
array([ 0,  1,  2,  3,  4,  7,  8, 11, 12, 13, 14, 15])

Затем, np.all(borders==0)даст вам необходимую информацию.


Примечание: это ломается для одномерных массивов, хотя я рассматриваю их как крайний случай. Тебе, наверное, лучше просто проверить эти два вопроса

Лукас Талер
источник
Это занимает время, пропорциональное общему количеству элементов в массиве, а не только границе. Кроме того, одномерные массивы не являются несущественным краевым случаем.
user2357112 поддерживает Monica
1
Кроме того, np.arange(15)не включает 15.
user2357112 поддерживает Monica
Я согласен с тем, что «неуместно» - это сильная формулировка, хотя я чувствую, что вам лучше просто проверить две относящиеся точки для одномерного массива. 15 опечатка, хороший улов
Лукас Талер