controlla se un array numpy ha 0 su tutti i suoi bordi [chiuso]


13

Quale sarebbe il modo più veloce per verificare se un array numpy multidimensionale ha 0 su tutti i lati.

Quindi, per un semplice esempio 2D, ho:

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

Mentre questo va bene per i casi 2D a destra, scrivere per dimensioni più alte è un po 'noioso e mi chiedevo se ci sia qualche trucco intelligente e insipido che posso usare qui per renderlo efficiente e anche più mantenibile.


8
Non np.all (x[:, 0] == 0)sarebbe più sicuro della somma? Il test di somma è corretto solo se tutti i numeri sono positivi.
Demi-Lune,


1
@ Demi-Lume Ha senso. Nel mio caso, tutto sarà> = 0 ma il tuo commento è apprezzato :)
Luca

1
In un caso 3D, intendi le facce (ce ne sono sei) o i bordi (ce ne sono 12) del cubo?
Riccardo Bucco,

@RiccardoBucco Sì, 6 facce. ma il mio problema è che può andare oltre la dimensione 3.
Luca

Risposte:


7

Ecco come puoi farlo:

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

np.take fa la stessa cosa dell'indicizzazione "fantasia".


1
@Luca: la documentazione non chiarisce, ma ne numpy.takefa una copia. Ciò potrebbe causare prestazioni peggiori del codice in base a una vista. (Il tempismo sarebbe necessario per essere sicuri - l'efficienza della visualizzazione NumPy a volte è strana.)
user2357112 supporta Monica

1
@RiccardoBucco: len(x.shape)può essere scritto più semplicemente come x.ndim.
user2357112 supporta Monica

1
@utente2357112supportsMonica grazie, l'ho risolto :)
Riccardo Bucco il

5
Inoltre, l'uso della comprensione di un elenco impedisce il allcorto circuito. È possibile rimuovere le parentesi quadre per utilizzare un'espressione del generatore, consentendo alldi tornare non appena viene restituita una singola numpy.allchiamata False.
user2357112 supporta Monica

1
@ user2357112supportsMonica True !!
Riccardo Bucco,

5

Ecco una risposta che esamina effettivamente le parti dell'array che ti interessano e non perde tempo a costruire una maschera delle dimensioni dell'intero array. C'è un loop a livello di Python, ma è breve, con iterazioni proporzionali al numero di dimensioni anziché alle dimensioni dell'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

Ci sono circostanze in cui not (view[0] == 0).all()non è equivalente a view[0].any()?
Paul Panzer,

@PaulPanzer: suppongo view[0].any()che funzionerebbe anche. Non sono del tutto sicuro delle implicazioni sull'efficienza del casting e del buffering coinvolti nelle due opzioni: view[0].any()teoricamente potrebbero essere implementate più rapidamente, ma ho già visto strani risultati prima e non capisco completamente il buffering in questione.
user2357112 supporta Monica

Suppongo che view[0].view(bool).any()sarebbe la soluzione ad alta velocità.
Paul Panzer,


(Inoltre, se argmaxo any, utilizzare una vista booleana significa gestire lo zero negativo come diverso da zero normale.)
user2357112 supporta Monica

2

Ho rimodellato l'array e poi ho iterato. Sfortunatamente, la mia risposta presuppone che tu abbia almeno tre dimensioni e sbaglierà per le matrici normali, dovresti aggiungere una clausola speciale per le matrici a forma 1 e 2 dimensionale. Inoltre, questo sarà lento, quindi ci sono probabilmente soluzioni migliori.

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

Che produrrà

>>> False
>>> True

Fondamentalmente impilo tutte le dimensioni una sopra l'altra e poi guardo attraverso di esse per controllare i loro bordi.


Questo esamina le parti sbagliate dell'array. Per un array tridimensionale, vogliamo esaminare le facce dell'intero array, non i bordi di ciascun subarray bidimensionale.
user2357112 supporta Monica

Ah, ha più senso. Ho frainteso
lwileczek il

1

forse l'operatore con i puntini di sospensione è quello che stai cercando, che funzionerà per molte dimensioni:

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)

Questo non colora tutte le facce. Ad esempio, provalo con un cubo (4, 4, 4).
Luca

Non sono sicuro di cosa intendi per colorare i volti, ma funziona se fai x (4, 4, 4)
daveg

1

È possibile utilizzare slicee mascheramento booleano per completare il lavoro:

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

Questa funzione modella prima il "nucleo" dell'array nella tupla s, quindi crea una maschera che mostra Truesolo per i punti confinanti. L'indicizzazione booleana fornisce quindi i punti di confine.

Esempio funzionante:

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

Quindi, np.all(borders==0)ti darà le informazioni desiderate.


Nota: questo si interrompe per gli array unidimensionali, anche se li considero un caso limite. Probabilmente stai meglio solo controllando i due punti in questione lì


Ciò richiede un tempo proporzionale al numero totale di elementi nell'array, anziché solo al bordo. Inoltre, le matrici unidimensionali non sono un caso limite irrilevante.
user2357112 supporta Monica

1
Inoltre, np.arange(15)non include 15.
user2357112 supporta Monica

Sono d'accordo sul fatto che "irrilevante" sia una formulazione forte, sebbene ritenga che tu stia meglio controllando i due punti relativi a un array 1d. Il 15 è un refuso, buona cattura
Lukas Thaler il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.