Nelle lezioni precedenti, abbiamo esplorato le potenzialità di Numpy familiarizzando con i tipi di dati offerti e gestiti da questo framework e prendendo un po’ di confidenza con la principale struttura dati, gli ndarray
.
In questa lezione, vedremo come effettuare delle semplici ma estremamente utili operazioni di creazione e manipolazione degli ndarray
, che torneranno utili non solo a livello di calcolo matematico ma anche quando vorrete utilizzare i tensori con framework di machine learning, come scikit learn, e di deep learning, come TensorFlow o PyTorch.
Creazione di un ndarray
Numpy offre diversi metodi per la creazione di nuovi ndarray
. Vediamoli insieme.
Per la creazione di una matrice composta da soli zeri, è possibile invocare il metodo np.zeros()
.
zeros_matrix = np.zeros((3,4))
zeros_matrix
array([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]])
Come possiamo vedere dall’output, la matrice risultante sarà composta da elementi di tipo float64
, ma è possibile modificarne il tipo specificando il valore della keyword dtype
.
In modo analogo, è possibile creare una matrice composta da soli uno tramite il metodo np.ones()
.
ones_matrix = np.ones((3,2))
ones_matrix
array([[1., 1.],
[1., 1.],
[1., 1.]])
Come nel caso precedente, anche in questo avremo una matrice i cui elementi sono di tipo float64
.
Se invece è necessario inizializzare una matrice ad uno specifico valore è possibile impiegare il metodo np.full()
.
full_matrix = np.full((2,3), 5)
full_matrix
array([[5, 5, 5],
[5, 5, 5]])
In questo caso specifico, è stato sufficiente specificare la shape
della matrice sotto forma di tupla e il valore per popolarla. In questo caso, la matrice generata ha elementi di tipo int64
ma, come per i precedenti metodi, è possibile modificarne il valore tramite la definizione della keyword dtype
.
Spesso, nei calcoli matriciali, è però necessario lavorare anche con le matrici di identità, che ricordiamo sono matrici NxN
la cui diagonale è composta da soli uno. Per crearla, è sufficiente utilizzare il metodo np.eye()
specificando la dimensione della matrice. Ad esempio, per creare una matrice di identità 3x3
:
identity_matrix = np.eye(3)
identity_matrix
array([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
La matrice risultante avrà valori di tipo float64
.
Qualora invece fosse necessario definire una matrice diagonale, ossia una matrice quadrata in cui solamente i valori della diagonale principale possono essere diversi da 0
, è possibile usare np.diag()
specificando i valori desiderati sulla diagonale.
diagonal_matrix = np.diag([1,2,3,4])
diagonal_matrix
array([[1, 0, 0, 0],
[0, 2, 0, 0],
[0, 0, 3, 0],
[0, 0, 0, 4]])
Altri due approcci estremamente utili per creare vettori con elementi che ricadono in uno specifico intervallo sono np.arange()
e np.linspace()
.
La funzione np.arange()
può prendere in input uno o più argomenti che ne modificano l’output finale. Ad esempio, con un solo argomento di tipo numerico, N
, creerà un ndarray
che va nel range [0, N-1
.
vect1 = np.arange(10)
vect1
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
Diversamente, specificando due input, con il primo minore del secondo, verrà creato un ndarray
nell’intervallo di valori specificato. Vediamo un esempio.
vect2 = np.arange(4, 9)
vect2
array([4, 5, 6, 7, 8])
Come si può facilmente intuire l’estremo superiore, 9
, non è incluso nella creazione del vettore.
Infine, questo metodo permette anche di definire un passo che identifica la distanza tra due valori adiacenti.
vect = np.arange(1,14,3)
vect
array([ 1, 4, 7, 10, 13])
Nonostante l’utilità di np.arange()
, spesso è opportuno utilizzare la funzione np.linspace()
per la sua precisione dei valori in virgola mobile. Vediamo un esempio.
vect3 = np.linspace(0,25,10)
vect3
array([ 0. , 2.77777778, 5.55555556, 8.33333333, 11.11111111,
13.88888889, 16.66666667, 19.44444444, 22.22222222, 25. ]
In questo caso abbiamo definito un vettore di tipo float64
con 10 valori nell’intervallo [0, 25]
opportunamente distanziati.
Infine, Numpy offre la possibilità di creare vettori e matrici in modo randomico sfruttando il modulo random
. Ad esempio, se volessimo creare una matrice quadrata 3x3
composta da valori float64
, basterà usare il metodo random()
:
rand_matrix = np.random.random((3,3))
rand_matrix
array([[0.38092162, 0.22859676, 0.96004006],
[0.6855616 , 0.70534742, 0.05605433],
[0.72397115, 0.40938473, 0.94926686]])
Invece, se volessimo una matrice quadrata 3x3
di soli valori interi in un dato intervallo, useremmo il metodo randint()
come segue.
rand_matrix = np.random.randint(4, 13, (3,3))
rand_matrix
array([[ 6, 9, 8],
[11, 11, 6],
[12, 4, 6]])
Se ancora volessimo creare un ndarray
che definisca un particolare tipo di distribuzione, come ad esempio una distribuzione normale gaussiana, possiamo usare metodi come normal()
.
X = np.random.normal(0, 0.1, size=(1000,1000))
In questo caso, viene creata una matrice 1000x1000
con valori randomici in virgola mobile che rappresentano una distribuzione Gaussiana con media nulla e deviazione standard di 0.1.
Quanto riportato finora è solo una rappresentazione dei principali metodi offerti da Numpy per la creazione di ndarray
.
Modifica della shape
Quando si lavora con gli ndarray
, è spesso necessario modificarne la forma (shape
) per poter fare determinate elaborazioni. Le modifiche più semplici riguardano la conversione di un vettore di N
elementi in una matrice. Per farlo, è necessario ricorrere alla funzione np.reshape()
. Vediamo un esempio:
vector = np.arange(20)
vect2matrix = np.reshape(vector, (4,5))
vect2matrix
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19]])
Al contrario, se volessimo trasformare una matrice in un vettore, basterebbe usare il metodo flatten()
proprio della classe ndarray
, come mostrato di seguito.
vect2matrix.flatten()
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
In questo caso, abbiamo convertito la matrice in un vettore concatenando le righe tra loro. Se volessimo fare la conversione concatenando le colonne, basterebbe specificare l’ordine tramite la keyword order
e la lettera F
.
vect2matrix.flatten(order='F')
array([ 0, 5, 10, 15, 1, 6, 11, 16, 2, 7, 12, 17, 3, 8, 13, 18, 4, 9, 14, 19])
Aggiunta e rimozione di elementi
Quando si lavora con gli ndarray
, è spesso necessario aggiungere o rimuovere elementi. Vediamo come, definendo un vettore e una matrice di esempio.
vect = np.array([1, 2, 3, 4, 5])
matrix = np.array([[1,2,3],[4,5,6],[7,8,9]])
Iniziamo con la rimozione di un elemento da un vettore tramite il metodo np.delete()
, specificando il vettore e la lista di indici in cui si trovano gli elementi che vogliamo rimuovere dal vettore.
np.delete(vect, [0,2])
array([2, 4, 5])
In questo caso, abbiamo rimosso dal vettore gli elementi in posizione 0
e 2
.
Con le matrici il discorso si complica leggermente, in quanto va specificato se deve essere cancellata una riga o una colonna tramite l’utilizzo della keyword axis
.
Per cancellare, ad esempio, la prima riga della nostra matrice basterà:
np.delete(matrix, [0,0], axis=0)
array([[4, 5, 6],
[7, 8, 9]])
Mentre, per cancellare la prima colonna, sarà sufficiente impostare il valore di axis=1
.
Per quanto riguarda l’aggiunta di dati a un ndarray
, ci sono due possibilità:
- l’operazione di appending, che aggiunge in coda a un
ndarray
i nuovi dati; - l’operazione di inserting, che aggiunge in un punto qualsiasi di un
ndarray
i valori di interesse.
Partiamo dall’operazione di appending, che può essere eseguita tramite la funzione np.append()
. Ad esempio, per aggiungere un valore in coda a un vettore, basta passare in input il vettore e il nuovo dato.
np.append(vect, 6)
array([1, 2, 3, 4, 5, 6])
Per aggiungere nuovi dati a una matrice, è necessario specificare non solo la matrice e i dati, ma anche se aggiungerli sulle righe o sulle colonne. Ad esempio, per aggiungere una nuova riga:
np.append(matrix, [[10, 11, 12]], axis=0)
array([[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12]])
Spesso però, si ha l’esigenza di inserire dei nuovi dati in un ndarray
in una determinata posizione. Per farlo, il modulo numpy
offre il metodo np.insert()
.
Ad esempio, se volessimo aggiungere al nostro vettore i valori 10
e 11
in posizione 2
, basterebbe passare i seguenti input al metodo insert()
.
np.insert(vect, 2, [10, 11])
array([ 1, 2, 10, 11, 3, 4, 5])
Se il nostro ndarray
è invece una matrice, come sempre dovremo specificare se aggiungere quei valori sulle righe o sulle colonne tramite la keyword axis
. Ad esempio, se volessimo aggiungere dopo la prima riga il vettore [10,11,12]
basterebbe:
np.insert(matrix,1,[10,11,12], axis=0)
array([[ 1, 2, 3],
[10, 11, 12],
[ 4, 5, 6],
[ 7, 8, 9]])
Il codice di questa lezione, con alcuni esempi in più, è disponibile su GitHub.