periksa apakah array numpy memiliki 0 pada semua batasnya [ditutup]

13

Apa yang akan menjadi cara tercepat untuk memeriksa apakah array numpy multidimensi memiliki 0 di semua sisi.

Jadi, untuk contoh 2D sederhana, saya punya:

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

Walaupun ini oke untuk kasus 2D ke kanan, menulis untuk dimensi yang lebih tinggi agak membosankan dan saya bertanya-tanya apakah ada beberapa trik numpy yang pintar yang dapat saya gunakan di sini untuk membuatnya efisien dan juga lebih dapat dipertahankan.

Luca
sumber
8
Bukankah np.all (x[:, 0] == 0)lebih aman dari jumlah? Tes jumlah itu benar hanya jika semua angka positif.
Demi-Lune
1
@ Demi-Lume Masuk akal. Dalam kasus saya, semuanya akan> = 0 tetapi komentar Anda dihargai :)
Luca
1
Dalam kasus 3D, maksud Anda wajah (ada enam) atau ujung (ada 12) kubus?
Riccardo Bucco
@RiccardoBucco Ya, 6 wajah. tapi masalah saya adalah dimensi itu bisa lebih tinggi dari 3.
Luca

Jawaban:

7

Inilah cara Anda dapat melakukannya:

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

np.take melakukan hal yang sama dengan pengindeksan "mewah".

Riccardo Bucco
sumber
1
@Luca: Dokumentasi tidak memperjelas, tetapi numpy.takemembuat salinan. Ini dapat menyebabkan kinerjanya lebih buruk daripada kode berdasarkan pada tampilan. (Pengaturan waktu perlu dipastikan - Efisiensi tampilan NumPy terkadang aneh.)
user2357112 mendukung Monica
1
@RiccardoBucco: len(x.shape)dapat ditulis lebih sederhana sebagai x.ndim.
user2357112 mendukung Monica
1
@ user2357112supportsMonica terima kasih, saya memperbaikinya :)
Riccardo Bucco
5
Juga, penggunaan daftar pemahaman mencegah alldari hubungan arus pendek. Anda bisa menghapus tanda kurung untuk menggunakan ekspresi generator, yang memungkinkan alluntuk kembali segera setelah satu numpy.allpanggilan kembali False.
user2357112 mendukung Monica
1
@ user2357112supportsMonica Benar !!
Riccardo Bucco
5

Inilah jawaban yang benar-benar memeriksa bagian-bagian dari array yang Anda minati, dan tidak membuang waktu untuk membuat topeng ukuran seluruh array. Ada loop tingkat Python, tapi pendek, dengan iterasi sebanding dengan jumlah dimensi, bukan ukuran array.

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 mendukung Monica
sumber
Apakah ada keadaan di mana not (view[0] == 0).all()tidak setara dengan view[0].any()?
Paul Panzer
@ PaulPanzer: Saya kira view[0].any()akan bekerja juga. Saya tidak sepenuhnya yakin tentang implikasi efisiensi dari casting dan buffering yang terlibat dalam dua opsi - view[0].any()secara teoritis dapat diimplementasikan lebih cepat, tetapi saya telah melihat hasil yang aneh sebelumnya, dan saya tidak sepenuhnya memahami buffering yang terlibat.
user2357112 mendukung Monica
Saya kira view[0].view(bool).any()akan menjadi solusi berkecepatan tinggi.
Paul Panzer
@ PaulPanzer: argmaxmungkin benar-benar mengalahkan anytampilan boolean . Hal ini jadi aneh.
user2357112 mendukung Monica
(Juga, apakah argmaxatau any, menggunakan tampilan boolean berarti menangani nol negatif sebagai tidak sama dengan nol biasa.)
user2357112 mendukung Monica
2

Saya membentuk kembali array dan kemudian mengulanginya. Sayangnya, jawaban saya mengasumsikan Anda memiliki setidaknya tiga dimensi dan akan error untuk matriks normal, Anda harus menambahkan klausa khusus untuk array berbentuk 1 & 2 dimensi. Selain itu, ini akan lambat sehingga kemungkinan ada solusi yang lebih baik.

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

Yang akan menghasilkan

>>> False
>>> True

Pada dasarnya saya menumpuk semua dimensi di atas satu sama lain dan kemudian melihat melalui mereka untuk memeriksa tepinya.

lwileczek
sumber
Ini memeriksa bagian array yang salah. Untuk array 3 dimensi, kami ingin memeriksa wajah seluruh array, bukan tepi masing-masing subarray 2 dimensi.
user2357112 mendukung Monica
Ah, itu lebih masuk akal. Saya salah paham
lwileczek
1

mungkin operator ellipsis adalah apa yang Anda cari, yang akan bekerja untuk banyak dimensi:

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
sumber
Ini tidak akan mewarnai semua wajah. Misalnya, coba dengan kubus (4, 4, 4).
Luca
Saya tidak yakin apa yang Anda maksud dengan mewarnai wajah, tetapi itu berfungsi jika Anda membuat x (4, 4, 4)
daveg
1

Anda dapat menggunakan slicedan boolean masking untuk menyelesaikan pekerjaan:

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])

Fungsi ini pertama-tama membentuk "inti" dari array ke dalam tuple s, dan kemudian membangun topeng yang Truehanya menunjukkan untuk titik yang berbatasan. Pengindeksan Boolean kemudian memberikan poin perbatasan.

Contoh kerja:

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])

Kemudian, np.all(borders==0)akan memberi Anda informasi yang diinginkan.


Catatan: ini pecah untuk array satu dimensi, meskipun saya menganggap itu kasus tepi. Anda mungkin lebih baik hanya memeriksa dua poin yang dimaksud di sana

Lukas Thaler
sumber
Ini membutuhkan waktu proporsional dengan jumlah elemen dalam array, bukan hanya perbatasan. Juga, array satu dimensi bukanlah kasus tepi yang tidak relevan.
user2357112 mendukung Monica
1
Juga, np.arange(15)tidak termasuk 15.
user2357112 mendukung Monica
Saya setuju bahwa "tidak relevan" adalah kata-kata yang kuat, meskipun saya merasa Anda lebih baik hanya memeriksa dua hal terkait untuk array 1d. 15 adalah kesalahan ketik, tangkapan yang bagus
Lukas Thaler