Nella lezione precedente, abbiamo visto e analizzato la classe dtype
, i principali tipi di dati e come definirli per poter lavorare al meglio con questa libreria e per fare conversioni di dati e calcoli matematici in modo semplice ed efficace.
In questa lezione, invece, ci concentreremo sulla principale struttura dati alla base di questo framework: gli n-dimensional array.
Gli n-dimensional array
Un n-dimensional array
(ndarry
) è un tipo di struttura dati che ha reso Numpy la libreria Python perfetta per il calcolo matematico, ottimizzando le performance in termini di costo computazionale e velocità.
Volendo utilizzare la definizione offerta dalla documentazione ufficiale, un ndarry
rappresenta un array multidimensionale e omogeneo di elementi di dimensioni fisse. Un tipo di dato associato all’oggetto array descrive il formato di ogni elemento presente in esso, tra cui:
- il suo ordine di byte;
- quanti byte occupa in memoria;
- se è un numero intero, un numero in virgola mobile o altro.
Possiamo definire più semplicemente un ndarray
N
Il tipo di elementi di questa particolare struttura dati è specificato da un dtype associato all’intero ndarray
Per creare un ndarry bidimensionale, ossia una matrice, composto da 3 righe e 3 colonne di float, basterà utilizzare il metodo array
numpy
import numpy as np
matrix = np.array([[1.2, 2.4, 3.1], [4.5, 5.2, 6.9], [7.2, 8.8, .9]], np.float32)
La variabile matrix
ndarray
type
type(matrix)
numpy.ndarray
A questo punto, per conoscerne la dimensione basterà utilizzare l’attributo shape
matrix.shape
(3, 3)
Invece, per verificarne il tipo, basterà usare l’attributo dtype
matrix.dtype
dtype('float32')
Infine, se vogliamo conoscere il numero di elementi e il numero di dimensioni che compongono l’oggetto matrix
size
ndim
print(f'matrix dimension: {matrix.ndim} - total elements: {matrix.size}')
matrix dimension: 2 - total elements: 9
Quelli visti fin qui sono solo alcuni degli attributi offerti dall’oggetto ndarray
imag
real
documentazione ufficiale
Come con altre strutture dati offerte da Python, con gli ndarray
- accedere al contenuto di un
ndarray
- modificare il contenuto di un
ndarray
indexing - modificare un
ndarray
slicing
Ad esempio, per accedere ad uno specifico elemento della matrice, basterà indicare l’indice per la riga e per la colonna della posizione a cui si vuole accedere.
matrix[1,2]
6.9
Nelle prossime lezioni, vedremo nelle specifico le diverse operazioni che possono essere compiute sugli ndarray
.
Gestione della memoria interna di un ndarray
A un'istanza della classe ndarray
- l'allocazione della memoria insieme allo schema di indicizzazione mappa quindi N-interi su un elemento del blocco dell'array;
- il range di variazione dell'indice è dato dalla forma dell'array e viceversa;
- il tipo di dato viene utilizzato per definire quanti byte richiederà ogni elemento dell’
ndarray
- il tipo di dato permette di definire come verranno inferiti i byte. Ad esempio, ogni elemento
int16
16/8 = 2 byte
Infine, un altro aspetto interessante degli ndarray
è che diversi ndarray
possono condividere gli stessi dati, in modo che le modifiche apportate in un ndarray
possano essere visibili in un altro. In questo caso, si parla di view
e di base
, dove:
- un
base ndarray
- un
view ndarray
ndarray
ndarray
Una qualsiasi modifica fatta su un base
o un view ndarray
verrà vista sull’altro.
Per creare una view, è possibile usare il metodo view()
di ndarray
e per sapere se effettivamente due ndarray
condividono la memoria, è possibile usare il metodo shares_memory()
del modulo numpy
. Vediamo un semplice esempio:
base = np.array([1, 2, 3, 4, 5])
view = base.view()
view[0] = 42
print(f'base: {base}')
print(f'view: {view}')
print(f'shared memory: {np.shares_memory(base, view)}')
# results
base: [42 2 3 4 5]
view: [42 2 3 4 5]
shared memory: True
ndarray vs list
Erroneamente, si tende a pensare che gli ndarray
siano simili alle list
in Python, ma le differenze sono notevoli. In Python un oggetto list
:
- è definito inserendo uno o più elementi tra parentesi quadre;
- è ordinato mostrando i dati in un ordine specifico;
- è mutabile;
- non deve essere dichiarato;
- non può gestire direttamente le operazioni matematiche.
Contrariamente gli ndarray
- devono essere dichiarati
- vengono creati tramite l’apposita funzione
array()
numpy
- possono definire un solo tipo di dato;
- possono memorizzare i dati in modo compatto, risultando efficienti per l’archiviazione di grandi quantità di dati;
- sono ottimi per le operazioni matematiche.
Ad esempio, si può dividere ogni elemento di un array per lo stesso numero con una sola riga di codice.
array = np.array([3, 6, 9, 12])
division = array/3
print(division)
[1. 2. 3. 4.]
print (type(division))
<class 'numpy.ndarray'>
Se provassimo a fare lo stesso con una list, otterremmo un errore.
list = [3, 6, 9, 12]
division = list/3
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-18-f127235414b2> in <module>()
1 list = [3, 6, 9, 12]
----> 2 division = list/3
TypeError: unsupported operand type(s) for /: 'list' and 'int'