copia array 2D nella terza dimensione, N volte (Python)


107

Vorrei copiare un array 2D numpy in una terza dimensione. Ad esempio, dato l'array numpy (2D):

import numpy as np
arr = np.array([[1,2],[1,2]])
# arr.shape = (2, 2)

convertirlo in una matrice 3D con N di tali copie in una nuova dimensione. Agendo arrcon N = 3, l'uscita dovrebbe essere:

new_arr = np.array([[[1,2],[1,2]],[[1,2],[1,2]],[[1,2],[1,2]]])
# new_arr.shape = (3, 2, 2)

Risposte:


146

Probabilmente il modo più pulito è usare np.repeat:

a = np.array([[1, 2], [1, 2]])
print(a.shape)
# (2,  2)

# indexing with np.newaxis inserts a new 3rd dimension, which we then repeat the
# array along, (you can achieve the same effect by indexing with None, see below)
b = np.repeat(a[:, :, np.newaxis], 3, axis=2)

print(b.shape)
# (2, 2, 3)

print(b[:, :, 0])
# [[1 2]
#  [1 2]]

print(b[:, :, 1])
# [[1 2]
#  [1 2]]

print(b[:, :, 2])
# [[1 2]
#  [1 2]]

Detto questo, spesso puoi evitare di ripetere del tutto i tuoi array usando la trasmissione . Ad esempio, diciamo che volevo aggiungere un (3,)vettore:

c = np.array([1, 2, 3])

a a. Potrei copiare il contenuto di a3 volte nella terza dimensione, quindi copiare il contenuto di cdue volte sia nella prima che nella seconda dimensione, in modo che entrambi i miei array fossero (2, 2, 3), quindi calcolare la loro somma. Tuttavia, è molto più semplice e veloce farlo:

d = a[..., None] + c[None, None, :]

Qui a[..., None]ha forma (2, 2, 1)e c[None, None, :]ha forma (1, 1, 3)*. Quando calcolo la somma, il risultato viene 'trasmesso' lungo le dimensioni della dimensione 1, dandomi un risultato di forma (2, 2, 3):

print(d.shape)
# (2,  2, 3)

print(d[..., 0])    # a + c[0]
# [[2 3]
#  [2 3]]

print(d[..., 1])    # a + c[1]
# [[3 4]
#  [3 4]]

print(d[..., 2])    # a + c[2]
# [[4 5]
#  [4 5]]

La trasmissione è una tecnica molto potente perché evita l'overhead aggiuntivo coinvolto nella creazione di copie ripetute degli array di input in memoria.


* Anche se li ho inclusi per chiarezza, gli Noneindici in cnon sono effettivamente necessari - potresti anche farlo a[..., None] + c, cioè trasmettere un (2, 2, 1)array contro un (3,)array. Questo perché se uno degli array ha meno dimensioni dell'altro, solo le dimensioni finali dei due array devono essere compatibili. Per fare un esempio più complicato:

a = np.ones((6, 1, 4, 3, 1))  # 6 x 1 x 4 x 3 x 1
b = np.ones((5, 1, 3, 2))     #     5 x 1 x 3 x 2
result = a + b                # 6 x 5 x 4 x 3 x 2

Per verificare che questo dà davvero il risultato giusto, si può anche stampare b[:,:,0], b[:,:,1]e b[:,:,2]. Ogni fetta di terza dimensione è una copia della matrice 2D originale. Questo non è così ovvio solo a guardarlo print(b).
ely

Qual'è la differenza tra None e np.newaxis? Quando l'ho provato, ha dato lo stesso risultato.
monolite

1
@wedran Sono esattamente gli stessi - np.newaxisè solo un alias diNone
ali_m

27

Un altro modo è usare numpy.dstack. Supponendo di voler ripetere i a num_repeatstempi della matrice :

import numpy as np
b = np.dstack([a]*num_repeats)

Il trucco è avvolgere la matrice ain un elenco di un singolo elemento, quindi utilizzare l' *operatore per duplicare gli elementi in questo elenco num_repeatsvolte.

Ad esempio, se:

a = np.array([[1, 2], [1, 2]])
num_repeats = 5

Questo ripete la matrice di [1 2; 1 2]5 volte nella terza dimensione. Per verificare (in IPython):

In [110]: import numpy as np

In [111]: num_repeats = 5

In [112]: a = np.array([[1, 2], [1, 2]])

In [113]: b = np.dstack([a]*num_repeats)

In [114]: b[:,:,0]
Out[114]: 
array([[1, 2],
       [1, 2]])

In [115]: b[:,:,1]
Out[115]: 
array([[1, 2],
       [1, 2]])

In [116]: b[:,:,2]
Out[116]: 
array([[1, 2],
       [1, 2]])

In [117]: b[:,:,3]
Out[117]: 
array([[1, 2],
       [1, 2]])

In [118]: b[:,:,4]
Out[118]: 
array([[1, 2],
       [1, 2]])

In [119]: b.shape
Out[119]: (2, 2, 5)

Alla fine possiamo vedere che la forma della matrice è 2 x 2, con 5 fette nella terza dimensione.


Come si confronta reshape? Più veloce? dà la stessa struttura? È decisamente più ordinato.
Ander Biguri

@AnderBiguri non ho mai fatto un benchmark ... l'ho inserito qui principalmente per completezza. Sarà interessante prendere tempo e vedere le differenze.
rayryeng

1
Ho appena fatto img = np.dstack ([arr] * 3) e ha funzionato bene! Grazie
grazie a

1
Ho pensato di poter proporre un output visualizzato per l'efficienza. Essendo un vecchio post, le persone potrebbero averlo perso. Aggiunta una soluzione a questa domanda e risposta.
Divakar

1
IMHO è la soluzione più leggibile, ma sarebbe fantastico confrontarla con altri metodi di confronto.
mrgloom

16

Usa una visualizzazione e ottieni runtime gratuito! Estendi n-dimarray generici an+1-dim

Introdotto in NumPy1.10.0 , possiamo sfruttare numpy.broadcast_toper generare semplicemente una 3Dvista 2Dnell'array di input. Il vantaggio sarebbe l'assenza di sovraccarico di memoria aggiuntivo e il runtime praticamente gratuito. Ciò sarebbe essenziale nei casi in cui gli array sono grandi e possiamo lavorare con le visualizzazioni. Inoltre, questo funzionerebbe con n-dimcasi generici .

Userei la parola stackal posto di copy, poiché i lettori potrebbero confonderla con la copia di array che crea copie di memoria.

Impila lungo il primo asse

Se vogliamo impilare l'input arrlungo il primo asse, la soluzione con cui np.broadcast_tocreare la 3Dvista sarebbe:

np.broadcast_to(arr,(3,)+arr.shape) # N = 3 here

Impila lungo il terzo / ultimo asse

Per impilare l'input arrlungo il terzo asse, la soluzione per creare la 3Dvista sarebbe:

np.broadcast_to(arr[...,None],arr.shape+(3,))

Se abbiamo effettivamente bisogno di una copia in memoria, possiamo sempre aggiungerla .copy()lì. Quindi, le soluzioni sarebbero:

np.broadcast_to(arr,(3,)+arr.shape).copy()
np.broadcast_to(arr[...,None],arr.shape+(3,)).copy()

Ecco come funziona l'impilamento per i due casi, mostrato con le informazioni sulla forma per un caso campione:

# Create a sample input array of shape (4,5)
In [55]: arr = np.random.rand(4,5)

# Stack along first axis
In [56]: np.broadcast_to(arr,(3,)+arr.shape).shape
Out[56]: (3, 4, 5)

# Stack along third axis
In [57]: np.broadcast_to(arr[...,None],arr.shape+(3,)).shape
Out[57]: (4, 5, 3)

Le stesse soluzioni funzionerebbero per estendere un n-diminput per n+1-dimvisualizzare l'output lungo il primo e l'ultimo asse. Esploriamo alcuni casi più scuri -

Caso di input 3D:

In [58]: arr = np.random.rand(4,5,6)

# Stack along first axis
In [59]: np.broadcast_to(arr,(3,)+arr.shape).shape
Out[59]: (3, 4, 5, 6)

# Stack along last axis
In [60]: np.broadcast_to(arr[...,None],arr.shape+(3,)).shape
Out[60]: (4, 5, 6, 3)

Caso di input 4D:

In [61]: arr = np.random.rand(4,5,6,7)

# Stack along first axis
In [62]: np.broadcast_to(arr,(3,)+arr.shape).shape
Out[62]: (3, 4, 5, 6, 7)

# Stack along last axis
In [63]: np.broadcast_to(arr[...,None],arr.shape+(3,)).shape
Out[63]: (4, 5, 6, 7, 3)

e così via.

Tempistiche

Usiamo un 2Dcaso campione di grandi dimensioni, otteniamo i tempi e verifichiamo che l'output sia un file view.

# Sample input array
In [19]: arr = np.random.rand(1000,1000)

Dimostriamo che la soluzione proposta è davvero una vista. Useremo lo stacking lungo il primo asse (i risultati sarebbero molto simili per lo stacking lungo il terzo asse) -

In [22]: np.shares_memory(arr, np.broadcast_to(arr,(3,)+arr.shape))
Out[22]: True

Prendiamo i tempi per dimostrare che è praticamente gratuito -

In [20]: %timeit np.broadcast_to(arr,(3,)+arr.shape)
100000 loops, best of 3: 3.56 µs per loop

In [21]: %timeit np.broadcast_to(arr,(3000,)+arr.shape)
100000 loops, best of 3: 3.51 µs per loop

Essendo una vista, aumentando Nda 3a 3000non cambia nulla sui tempi ed entrambi sono trascurabili sulle unità di temporizzazione. Quindi, efficiente sia sulla memoria che sulle prestazioni!


3
A=np.array([[1,2],[3,4]])
B=np.asarray([A]*N)

Modifica @ Mr.F, per preservare l'ordine delle dimensioni:

B=B.T

Ciò si traduce in un array N x 2 x 2 per me, ad esempio B.shapestampe (N, 2, 2)per qualsiasi valore di N. Se trasponi Bcon B.T, corrisponde all'output atteso.
ely

@ Mr.F - Hai ragione. Questo trasmetterà lungo la prima dimensione, e così facendo B[0], B[1],...ti darà la fetta giusta, che B[:,:,0], B[:,:,1]
sosterrò

Potrebbe essere più facile da digitare, ma ad esempio se lo stai facendo con dati di immagine sarebbe in gran parte errato, poiché quasi tutti gli algoritmi si aspetteranno che le convenzioni dell'algebra lineare vengano utilizzate per le sezioni 2D dei canali pixel. È difficile immaginare un'applicazione in cui si inizia con un array 2D, trattando righe e colonne con una certa convenzione e quindi si desidera che più copie della stessa cosa si estendano in un nuovo asse, ma all'improvviso si desidera che il primo asse cambi significato in essere il nuovo asse ...
ely

@ Mr.F - Oh certamente. Non riesco a indovinare su quali applicazioni vorrai utilizzare la matrice 3D in futuro. Detto questo, tutto dipende dall'applicazione. FWIW, preferisco il B[:,:,i]così come questo è ciò a cui sono abituato.
Rayryeng

2

Ecco un esempio di trasmissione che fa esattamente ciò che è stato richiesto.

a = np.array([[1, 2], [1, 2]])
a=a[:,:,None]
b=np.array([1]*5)[None,None,:]

Quindi b*aè il risultato desiderato e (b*a)[:,:,0]produce array([[1, 2],[1, 2]]), che è l'originale a, come fa (b*a)[:,:,1], ecc.


2

Questo può ora essere ottenuto anche utilizzando np.tile come segue:

import numpy as np

a = np.array([[1,2],[1,2]])
b = np.tile(a,(3, 1,1))

b.shape
(3,2,2)

b
array([[[1, 2],
        [1, 2]],

       [[1, 2],
        [1, 2]],

       [[1, 2],
        [1, 2]]])
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.