Matrici - Numpy

Scarica zip esercizi

Naviga file online

ATTENZIONE

Gli esercizi che segueno contengono dei test con gli assert. Per capire come svolgerli, leggi prima Gestione errori e testing

Introduzione

Ci sono sostanzialmente due modi in Python di rappresentare matrici: come liste di liste, oppure con la libreria esterna numpy. La più usata è sicuramente numpy, vediamo il motivo e le principali differenze:

Liste di liste - vedere foglio separato

  1. native in Python

  2. non efficienti

  3. le liste sono pervasive in Python, probabilmente incontrerai matrici espresse come liste di liste in ogni caso

  4. forniscono un’idea di come costruire una struttura dati annidata

  5. possono servire per comprendere concetti importanti come puntatori alla memoria e copie

Numpy - questo foglio

  1. non nativamente disponibile in Python

  2. efficiente

  3. alla base di parecchie librerie di calcolo scientifico (scipy, pandas)

  4. la sintassi per accedere agli elementi è lievemente diversa da quella delle liste di liste

  5. in alcuni rari casi potrebbe portare problemi di installazione e/o conflitti (l’implementazione non è puro Python)

Qui vedremo i tipi di dati e comandi essenziali della libreria numpy, ma non ci addentreremo nei dettagli.

L’idea è semplicemente passare ad usare il formato dati ndarray senza badare molto alle performance: per esempio, anche se i cicli for in Python sono lenti perchè operano cella per cella, li useremo comunque. Qualora tu abbia effettivamente necessità di eseguire calcoli velocemente, vorrete usare operazioni su vettori ma per questo invitiamo alla lettura dei link qua sotto.

ATTENZIONE: se vuoi usare Numpy in Python tutor, invece dell’interprete di default Python 3.6 dovete selezionare Python 3.6 with Anaconda (che a Maggio 2019 risulta marcato come sperimentale)

Per riferimenti, vedere:

Che fare

  • scompatta lo zip in una cartella, dovresti ottenere qualcosa del genere:

matrices-numpy
    matrices-numpy.ipynb
    matrices-numpy-sol.ipynb
    jupman.py

ATTENZIONE: Per essere visualizzato correttamente, il file del notebook DEVE essere nella cartella szippata.

  • apri il Jupyter Notebook da quella cartella. Due cose dovrebbero aprirsi, prima una console e poi un browser. Il browser dovrebbe mostrare una lista di file: naviga la lista e apri il notebook matrices-numpy.ipynb

  • Prosegui leggendo il file degli esercizi, ogni tanto al suo interno troverai delle scritte ESERCIZIO, che ti chiederanno di scrivere dei comandi Python nelle celle successive. Gli esercizi sono graduati per difficoltà, da una stellina ✪ a quattro ✪✪✪✪

Scorciatoie da tastiera:

  • Per eseguire il codice Python dentro una cella di Jupyter, premi Control+Invio

  • Per eseguire il codice Python dentro una cella di Jupyter E selezionare la cella seguente, premi Shift+Invio

  • Per eseguire il codice Python dentro una cella di Jupyter E creare una nuova cella subito dopo, premi Alt+Invio

  • Se per caso il Notebook sembra inchiodato, prova a selezionare Kernel -> Restart

Creare una matrice

[1]:
# Innanzitutto importiamo la libreria, e per comodità la rinominiamo in 'np'

import numpy as np

Con le liste di liste abbiamo spesso costruito le matrici una riga alla volta, aggiundo liste all’occorrenza. In numpy invece di solito si crea in un colpo solo tutta la matrice, riempiendola di zeri.

In particolare, questo comando crea un ndarray riempito di zeri:

[2]:
mat = np.zeros( (2,3)  )   # 2 righe, 3 colonne
[3]:
mat
[3]:
array([[0., 0., 0.],
       [0., 0., 0.]])

Nota come all’interno di array( ) il contenuto sembra che venga rappresentato come una lista di liste, MA in realtà nelle memoria fisica i dati sono strutturati in una sequenza lineare che permette a Python di accedere ai numeri in modo molto più rapido.

Possiamo anche crearci un ndarray a partire da una lista di liste:

[4]:
mat = np.array( [ [5.0,8.0,1.0],
                  [4.0,3.0,2.0]])
[5]:
mat
[5]:
array([[5., 8., 1.],
       [4., 3., 2.]])
[6]:
type(mat)
[6]:
numpy.ndarray

Dimensioni di una matrice

Per ottenere le dimensioni, scriviamo così:

ATTENZIONE: dopo shape non ci sono le parentesi tonde !

shape è un attributo, non una funzione da chiamare

[7]:
mat = np.array( [ [5.0,8.0,1.0],
                  [4.0,3.0,2.0]])

mat.shape
[7]:
(2, 3)

Se vogliamo memorizzare le dimensioni in variabili separate, possiamo usare questo modo più pythonico: (notare la virgola tra num_righe e num_colonne):

[8]:
num_righe, num_colonne = mat.shape
[9]:
num_righe
[9]:
2
[10]:
num_colonne
[10]:
3

Lettura e scrittura

Per accedere ai dati o sovrascriverli si utilizza la notazione con le quadre, con l’importante differenza che in numpy è consentito scrivere entrambi gli indici dentro le stesse quadre, separati da una virgola:

ATTENZIONE: la notazione mat[i,j] è solo per numpy!

Con le liste di liste non funziona.

[11]:
mat = np.array( [ [5.0,8.0,1.0],
                  [4.0,3.0,2.0]])

# mettiamo il numero 0 nella cella alla riga 0 e colonna 1

mat[0,1] = 9
[12]:
mat
[12]:
array([[5., 9., 1.],
       [4., 3., 2.]])
[13]:
# Accediamo alla cella alla riga 0 e colonna 1

mat[0,1]
[13]:
9.0
[14]:
# mettiamo il numero 7 nella cella alla riga 1 e colonna 2

mat[1,2] = 7
[15]:
mat
[15]:
array([[5., 9., 1.],
       [4., 3., 7.]])

✪ ESERCIZIO: prova a scrivere così: che succede?

mat[0,0] = "c"
[16]:
# scrivi qui


[17]:
mat[1,1]
[17]:
3.0

✪ ESERCIZIO: Prova a scrivere così e vedere che succede:

mat[1,1.0]
Mostra soluzione
[18]:
# scrivi qui


NaN e infinità

I numeri float possono essere numeri, non numeri , e anche infinità . A volte durante i calcoli accadono condizioni estreme, come per esempio dividere un piccolo numero per un numero enorme. In tali casi, potresti finire con un float che è il temuto Not a Number , NaN in breve, o potresti ottenere una infinità . Questo potrebbe portare a comportamenti terribilmente imprevedibili, perciò devi saper riconoscere situazioni potenzialmente problematiche.

I comportamenti descritti in seguito sono dettati dallo standard IEEE per l’Aritmetica in virgola mobile binaria (IEEE 754) usato da Numpy e che è implementato in tutti i processori ( CPU ), perciò di fatto riguarda tutti i linguaggi di programmazione.

NaN

Un NaN dal nome Non è un Numero. Che è già un nome poco chiaro, visto che il NaN in realtà è un membro molto speciale dei floats, con questa stupefacente proprietà:

ATTENZIONE: NaN NON E’ UGUALE A SE’ STESSO !!!

Sì hai letto bene, NaN davvero non è uguale a sè stesso.

Persino se la tua mente vuole rifiutare questa nozione, la confermeremo a breve.

Per ottenere un NaN, puoi usare il modulo Python math che contiene questo oggetto alieno:

[19]:
import math
math.nan    # nota che stampa 'nan' con n minuscolo
[19]:
nan

Come detto, un NaN è considerato un float:

[20]:
type(math.nan)
[20]:
float

Eppure, si comporta molto diversamente dai suoi compagni float, o da ogni altro oggetto nelluniverso conosciuto:

[21]:
math.nan == math.nan   # Eh ????
[21]:
False

Rilevare i NaN

Dato quanto sopra, se vuoi controllare se una variabile x è un NaN, non puoi scrivere così:

[22]:
x = math.nan
if x == math.nan:  # SBAGLIATO
    print("Sono un NaN ")
else:
    print("x è qualcos'altro ??")
x è qualcos'altro ??

Per gestire correttamente questa situazione, devi usare la funzione math.isnan:

[23]:
x = math.nan
if math.isnan(x):  # CORRETTO
    print("x è un NaN ")
else:
    print("x è qualcos'altro ??")
x è un NaN

Nota che math.isnan funziona anche con NaN negativi:

[24]:
y = -math.nan
if math.isnan(y):  # CORRETTO
    print("y è un NaN ")
else:
    print("y è quacos'altro ??")
y è un NaN

Sequenze con i NaN

Per fortuna, non tutto è completamente assurdo. Se compari sequenze che contengono NaN ad altre, ottieni risultati ragionevoli:

[25]:
[math.nan, math.nan] == [math.nan, math.nan]
[25]:
True
[26]:
[math.nan, math.nan] == [math.nan, 5.0]
[26]:
False

Esercizio - NaN due variabili

Date due variabili x e y, scrivi del codice che stampa "stessa cosa" quando sono lo stesso, anche quando sono NaN. Altrimenti, stampa "non sono la stessa cosa"

Mostra soluzione
[27]:
import math

# output atteso: stessa cosa
x = math.nan
y = math.nan

# output atteso: non sono la stessa cosa
#x = 3
#y = math.nan

# output atteso: non sono la stessa cosa
#x = math.nan
#y = 5

# output atteso: non sono la stessa cosa
#x = 2
#y = 7

# output atteso: stessa cosa
#x = 4
#y = 4

# scrivi qui


stessa cosa

Operazioni sui NaN

Qualunque operazione sui NaN genera un altro NaN:

[28]:
5 * math.nan
[28]:
nan
[29]:
math.nan + math.nan
[29]:
nan
[30]:
math.nan / math.nan
[30]:
nan

L’unica cosa che non puoi fare è dividere per zero un NaN ‘fuori scatola’:

math.nan / 0
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-94-1da38377fac4> in <module>
----> 1 math.nan / 0

ZeroDivisionError: float division by zero

NaN corrisponde al valore logico booleano True:

[31]:
if math.nan:
    print("That's True")
That's True

I NaN e Numpy

Quando usi Numpy è abbastanza probabile incontrare NaN, al punto che sono ridefiniti dentro Numpy - ma di fatto sono esattamente gli stessi che nel modulo math:

[32]:
np.nan
[32]:
nan
[33]:
math.isnan(np.nan)
[33]:
True
[34]:
np.isnan(math.nan)
[34]:
True

In Numpy quando hai numeri sconosciuti potresti essere tentato di mettere un None. Puoi anche farlo, ma guarda attentamente il risultato:

[35]:
import numpy as np
np.array([4.9,None,3.2,5.1])
[35]:
array([4.9, None, 3.2, 5.1], dtype=object)

L’array risultante non è un array di float64 che permette calcoli veloci, invece è un array che contiene generici object , perchè Numpy assume che l’array contenga dati eterogenei. Perciò quello che guadagni in generalità lo perdi in performance, che dovrebbe essere il motivo principale di usare Numpy.

Per quanto appaiano strani, i NaN sono considerati come dei float e quindi possono essere salvati nell’array:

[36]:
np.array([4.9,np.nan,3.2,5.1])   # NOTA: il `dtype=object` è sparito
[36]:
array([4.9, nan, 3.2, 5.1])

Dove sono i NaN ?

Vediamo dove possiamo incontrare dei NaN e altri oggetti strani come le infinità.

Prima, controlliamo cosa succede quando chiamiamo la funzione log del modulo standard math. Dalle lezioni di matematica, sappiamo che la funzione log si comporta così:

  • \(x < 0\): non definita

  • \(x = 0\): tende a meno infinito

  • \(x > 0\): definita

log function u9u9u9

Perciò possiamo chiederci cosa succede se gli passiamo un valore per il quale non è definita. Proviamo prima con math.log della libreria standard di Python:

>>> math.log(-1)
ValueError                                Traceback (most recent call last)
<ipython-input-38-d6e02ba32da6> in <module>
----> 1 math.log(-1)

ValueError: math domain error

In questo caso viene sollevato ValueError e l’esecuzione viene interrotta.

Vediamo ora l’equivalente in Numpy:

[37]:
np.log(-1)
/home/da/.local/lib/python3.5/site-packages/ipykernel_launcher.py:1: RuntimeWarning: invalid value encountered in log
  """Entry point for launching an IPython kernel.
[37]:
nan

In questo caso abbiamo ottenuto come risultato np.nan, quindi l’esecuzione non si è interrotta, Jupyter ci ha solo informato con una stampa addizionale che abbiamo compiuto qualcosa di pericoloso.

Il comportamento di default di Numpy quando incontra calcoli pericolosi è effettuare in ogni caso il calcolo e salvare il risultato come NaN o altri oggetti limite. Questo vale anche per i calcoli sugli array:

[38]:
np.log(np.array([3,7,-1,9]))
/home/da/.local/lib/python3.5/site-packages/ipykernel_launcher.py:1: RuntimeWarning: invalid value encountered in log
  """Entry point for launching an IPython kernel.
[38]:
array([1.09861229, 1.94591015,        nan, 2.19722458])

Infinità

Come abbiamo detto in precedenza, Numpy usa lo standard IEEE per l’aritmetica binaria in virgola mobile (IEEE 754). Dato che qualcuno all’IEEE ha deciso di racchiudere i misteri dell’infinito nei numeri float, abbiamo ancora un’altro cittadino da considerare quando facciamo calcoli (per altre informazioni, vedere Numpy documentation on constants):

Infinità positiva np.inf

[39]:
 np.array( [ 5 ] ) / 0
/home/da/.local/lib/python3.5/site-packages/ipykernel_launcher.py:1: RuntimeWarning: divide by zero encountered in true_divide
  """Entry point for launching an IPython kernel.
[39]:
array([inf])
[40]:
np.array( [ 6,9,5,7 ] ) / np.array( [ 2,0,0,4 ] )
/home/da/.local/lib/python3.5/site-packages/ipykernel_launcher.py:1: RuntimeWarning: divide by zero encountered in true_divide
  """Entry point for launching an IPython kernel.
[40]:
array([3.  ,  inf,  inf, 1.75])

Attenzione che:

  • Not a Number non è equivalente all’infinità

  • l’infinità positiva non è equivalente all’infinità negativa

  • l’infinità è equivalente all’infinità positiva

Questa volta, l’infinità è equivalente all’infinità:

[41]:
np.inf == np.inf
[41]:
True

perciò possiamo in sicurezza equiparare due infinità con ==:

[42]:
x = np.inf

if x == np.inf:
    print("x è infinito")
else:
    print("x è finito")
x è infinito

Alternativamente, possiamo usare la funzione np.isinf:

[43]:
np.isinf(np.inf)
[43]:
True

Infinità negativa

Possiamo anche avere un’infinità negativa, che è differente dall’inifinità positiva:

[44]:
-np.inf == np.inf
[44]:
False

Nota che isinf può rilevare sia infinità positive che negative:

[45]:
np.isinf(-np.inf)
[45]:
True

Per rilevare specificamente un’infinità negativa dei usare isneginf:

[46]:
np.isneginf(-np.inf)
[46]:
True
[47]:
np.isneginf(np.inf)
[47]:
False

Dove possiamo trovarle? Come esempio, proviamo la funzione np.log:

[48]:
np.log(0)
/home/da/.local/lib/python3.5/site-packages/ipykernel_launcher.py:1: RuntimeWarning: divide by zero encountered in log
  """Entry point for launching an IPython kernel.
[48]:
-inf

Combinare infinità e NaN

Quando esegui operazioni che riguardano le infinità e i NaN, l’aritmetica IEEE prova a imitare l’analisi classica, a volte includendo NaN come risultato:

[49]:
np.inf + np.inf
[49]:
inf
[50]:
- np.inf - np.inf
[50]:
-inf
[51]:
np.inf * -np.inf
[51]:
-inf

Un risultato che in analisi classica sarebbe non definito, qui diventa NaN:

[52]:
np.inf - np.inf
[52]:
nan
[53]:
np.inf / np.inf
[53]:
nan

Come al solito, combinare con un NaN risulta in NaN:

[54]:
np.inf + np.nan
[54]:
nan
[55]:
np.inf / np.nan
[55]:
nan

Zero negativo

Puoi persino avere uno zero negativo - chi l’avrebbe pensato?

[56]:
np.NZERO
[56]:
-0.0

Lo zero negativo naturalmente fa coppia bene con il più conosciuto e apprezzato zero positivo:

[57]:
np.PZERO
[57]:
0.0

NOTA: Scrivere np.NZERO o -0.0 è esattamente la stessa cosa. Lo stesso vale per lo zero positivo.

A questo punto, potresti cominciare a chiederti con qualche se sono davvero considerati uguali. Verifichiamo:

[58]:
0.0 == -0.0
[58]:
True

Grandioso! Finalmente qualcosa che ha senso.

Dato quanto sopra, potresti pensare che in una formula puoi sostituire one per l’altro e ottenere gli stessi risultati, in armonia con le regole dell’universo.

Facciamo un tentativo di sostituzione, come esempio prima cercheremo di dividere un numero per uno zero positivo (persino se gli insegnanti di matematica ci dicono che tali divisioni siano vietate) - cosa potremmo mai ottenere?

\(\frac{5.0}{0.0}= ???\)

In termini di Numpy, potremmo scrivere così per ‘inscatolare’ tutto in arrays:

[59]:
np.array( [ 5.0 ] ) / np.array( [ 0.0 ] )
/home/da/.local/lib/python3.5/site-packages/ipykernel_launcher.py:1: RuntimeWarning: divide by zero encountered in true_divide
  """Entry point for launching an IPython kernel.
[59]:
array([inf])

Mmm, abbiamo ottenuto un array con dentro np.inf.

Se 0.0 e -0.0 sono davvero la stessa cosa, dividendo un numero per -0.0 dovremmo ottenere lo stesso identico risultato, no?

Proviamo:

[60]:
np.array( [ 5.0 ] ) / np.array( [ -0.0 ] )
/home/da/.local/lib/python3.5/site-packages/ipykernel_launcher.py:1: RuntimeWarning: divide by zero encountered in true_divide
  """Entry point for launching an IPython kernel.
[60]:
array([-inf])

Ecchecaspita. Questa volta ci ritroviamo con una infinità negativa -np.inf

Se tutto ciò ti pare strano, non dare la colpa a Numpy o Python. Questo è il modo con cui praticamente ogni processore ( CPU ) compie operazioni in virgola mobile, perciò lo troverai in quasi TUTTI i linguaggi di programmazione.

Quello che i linguaggi di programmazione possono fare è aggiungere ulteriori controlli per proteggerti da queste situazioni paradossali, come per esempio lanciare ZeroDivisionError quando scrivi direttamente 1.0/0.0 (bloccando quindi l’esecuzione) o stampare un warning nel caso di operazioni su array Numpy.

Esercizio: rilevare numeri propri

Scrivi del codice che STAMPA numeri uguali se due numeri x e y sono uguali e veri numeri, e STAMPA non uguali altrimenti.

NOTA: numeri non uguali va stampato se uno qualunque dei numeri è infinito o NaN.

Per risolverlo, sentiti libero di chiamare funzioni indicate nella documentazione di Numpy riguardo le costanti

Mostra soluzione
[61]:
import numpy as np

# atteso: numeri uguali
x = 5
y = 5

# atteso: numeri non uguali
#x = np.inf
#y = 3

# atteso: numeri non uguali
#x = 3
#y = np.inf

# atteso: numeri non uguali
#x = np.inf
#y = np.nan

# atteso: numeri non uguali
#x = np.nan
#y = np.inf

# atteso: numeri non uguali
#x = np.nan
#y = 7

# atteso: numeri non uguali
#x = 9
#y = np.nan

# atteso: numeri non uguali
#x = np.nan
#y = np.nan


# scrivi qui


numeri uguali
numeri uguali

Domande - NaN

Per ciascuna delle espressioni seguenti, prova ad indovinare il risultato

ATTENZIONE: ciò che segue può causare nausea e gravi convulsioni.

Durante i test clinici, sia pazienti con inclinazioni matematiche che soggetti con repulsione per le scienze esatte hanno lamentato malessere per ragioni differenti che sono ancora oggetto di ricerca.

a.  0.0 * -0.0
b.  (-0.0)**3
c.  np.log(-7) == math.log(-7)
d.  np.log(-7) == np.log(-7)
e.  np.isnan( 1 / np.log(1) )
f.  np.sqrt(-1) * np.sqrt(-1)   # sqrt = square root
g.  3 ** np.inf
h   3 ** -np.inf
i.  1/np.sqrt(-3)
j.  1/np.sqrt(-0.0)
m.  np.sqrt(np.inf) - np.sqrt(-np.inf)
n.  np.sqrt(np.inf) + ( 1 / np.sqrt(-0.0) )
o.  np.isneginf(np.log(np.e) / np.sqrt(-0.0))
p.  np.isinf(np.log(np.e) / np.sqrt(-0.0))
q.  [np.nan, np.inf] == [np.nan, np.inf]
r.  [np.nan, -np.inf] == [np.nan, np.inf]
s.  [np.nan, np.inf] == [-np.nan, np.inf]

Verifica comprensione

ATTENZIONE

Gli esercizi che seguono contengono dei test con gli assert. Per capire come svolgerli, leggi prima Gestione errori e testing

Prova adesso a implementare queste funzioni

quadro

✪✪✪ Restituisce una NUOVA matrice numpy di n righe e n colonne, in cui tutti i valori sono a zero eccetto quelli sui bordi, che devono essere uguali a k

Per esempio, quadro(4, 7.0) deve dare:

array([[7.0, 7.0, 7.0, 7.0],
       [7.0, 0.0, 0.0, 7.0],
       [7.0, 0.0, 0.0, 7.0],
       [7.0, 7.0, 7.0, 7.0]])

Ingredienti:

  • crea una matrice pieni di zeri. ATTENZIONE: quali dimensioni ha? Bisogna usare n o k ? Leggi BENE il testo.

  • comincia riempiendo le celle della prima riga con i valori k. Per iterare lungo le colonne della prima riga, usa un for j in range(n)

  • riempi le altre righe e colonne, usando opportuni for

Mostra soluzione
[62]:
def quadro(n, k):
    raise Exception('TODO IMPLEMENT ME !')



r1 = np.array( [[7.0, 7.0, 7.0, 7.0],
                [7.0, 0.0, 0.0, 7.0],
                [7.0, 0.0, 0.0, 7.0],
                [7.0, 7.0, 7., 7.0]])

# all_close ritorna True se tutti i valori nella prima matrice sono abbastanza vicini
# (cioè entro una certa tolleranza) ai corrispondenti nella seconda
assert np.allclose(quadro(4, 7.0), r1)

r2 = np.array( [ [7.0] ])
assert np.allclose(quadro(1, 7.0), r2)

r3 = np.array( [ [7.0, 7.0],
                 [7.0, 7.0]])
assert np.allclose(quadro(2, 7.0), r3)

media_righe

✪✪✪ Prende una matrice numpy n x m e RITORNA una NUOVA matrice numpy di una sola colonna in cui i valori sono la media dei valori nelle corrispondenti righe della matrice in input

Esempio:

Input: matrice 5x4

3 2 1 4
6 2 3 5
4 3 6 2
4 6 5 4
7 2 9 3

Output: matrice 5x1

(3+2+1+4)/4
(6+2+3+5)/4
(4+3+6+2)/4
(4+6+5+4)/4
(7+2+9+3)/4

Ingredienti:

  • create una matrice n x 1 da ritornare, riempiendola di zeri

  • visitate tutte le celle della matrice originale con due for in range annidati

  • durante la visita, accumulate nella matrice da ritornare la somma degli elementi presi da ciascuna riga della matrice originale

  • una volta completata la somma di una riga, potete dividerla per la dimensione ncolonne della matrice originale

  • ritornate la matrice

Mostra soluzione
[63]:
def media_righe(mat):
    raise Exception('TODO IMPLEMENT ME !')
    return ret

m1 = np.array([  [5.0]  ])
r1 = np.array([  [5.0]  ])
assert np.allclose(media_righe(m1), r1)

m2 = np.array([ [5.0, 3.0] ])
r2 = np.array([ [4.0]  ])
assert np.allclose(media_righe(m2), r2)

m3 = np.array(
    [[3,2,1,4],
     [6,2,3,5],
     [4,3,6,2],
     [4,6,5,4],
     [7,2,9,3]])

r3 = np.array([ [(3+2+1+4)/4],
                [(6+2+3+5)/4],
                [(4+3+6+2)/4],
                [(4+6+5+4)/4],
                [(7+2+9+3)/4] ])

assert np.allclose(media_righe(m3), r3)

matrot

✪✪✪ RITORNA una NUOVA matrice numpy che ha i numeri della matrice numpy di input ruotati di una colonna.

Per ruotati intendiamo che:

  • se un numero nella matrice di input si trova alla colonna j, nella matrice di output si troverà alla colonna j+1 nella stessa riga.

  • Se un numero si trova nell’ultima colonna, nella matrice di output si troverà nella colonna zeresima.

Esempio:

Se abbiamo come input

np.array(   [
                [0,1,0],
                [1,1,0],
                [0,0,0],
                [0,1,1]
            ])

Ci aspettiamo come output

np.array(   [
                [0,0,1],
                [0,1,1],
                [0,0,0],
                [1,0,1]
            ])
Mostra soluzione
[64]:
import numpy as np

def matrot(matrice):
    raise Exception('TODO IMPLEMENT ME !')

m1 = np.array(  [ [1] ])
r1 = np.array( [  [1]  ])
assert np.allclose(matrot(m1), r1)

m2 = np.array(  [ [0,1] ])
r2 = np.array( [ [1,0] ])
assert np.allclose(matrot(m2), r2)

m3 = np.array([ [0,1,0] ])
r3 = np.array([ [0,0,1] ])
assert np.allclose(matrot(m3), r3)

m4 = np.array(  [[0,1,0],
                 [1,1,0]])
r4 = np.array(  [[0,0,1],
                 [0,1,1]])
assert np.allclose(matrot(m4), r4)


m5 = np.array([ [0,1,0],
                [1,1,0],
                [0,0,0],
                [0,1,1]])
r5 = np.array([ [0,0,1],
                [0,1,1],
                [0,0,0],
                [1,0,1]])
assert np.allclose(matrot(m5), r5)

disp

✪✪✪ Prende una matrice Numpy mat di dimensioninrighe x ncol contenente numeri interi in input e RITORNA una NUOVA matrice numpy di dimensioni nrighe x ncol che è come quella originale, ma nelle celle che contenevano numeri pari adesso ci saranno numero dispari ottenuti sommando 1 al numero pari esistente.

Esempio:

disp(np.array( [
                    [2,5,6,3],
                    [8,4,3,5],
                    [6,1,7,9]
               ]))

Deve dare in output

array([[ 3.,  5.,  7.,  3.],
       [ 9.,  5.,  3.,  5.],
       [ 7.,  1.,  7.,  9.]])

Suggerimenti:

  • Visto che dovete ritornare una nuova matrice, cominciate con il crearne una vuota

  • scorrete con indici i e j tutta la matrice iniziale

Mostra soluzione
[65]:
import numpy as np

def disp(mat):
    raise Exception('TODO IMPLEMENT ME !')

m1 = np.array([ [2] ])
r1 = np.array([ [3] ])
assert np.allclose(disp(m1), r1)
assert m1[0][0] == 2  # controlla non si stia modificando la matrice originale

m2 = np.array( [ [2,5,6,3],
                 [8,4,3,5],
                 [6,1,7,9]
               ])
r2 = np.array( [ [3,5,7,3],
                 [9,5,3,5],
                 [7,1,7,9]])
assert np.allclose(disp(m2), r2)


radalt

✪✪✪ Prende una matrice numpy mat di dimensioninrighe x ncol contenente numeri interi in input e RITORNA una NUOVA matrice numpy di dimensioni nrighe x ncol, avente alle righe di indice pari i numeri della matrice originale moltiplicati per due, e alle righe di indice dispari gli stessi numeri della matrice originale

Esempio:

m  = np.array( [                      #  indice
                    [ 2, 5, 6, 3],    #    0     pari
                    [ 8, 4, 3, 5],    #    1     dispari
                    [ 7, 1, 6, 9],    #    2     pari
                    [ 5, 2, 4, 1],    #    3     dispari
                    [ 6, 3, 4, 3]     #    4     pari
               ])

Una chiamata a

radalt(m)

ritornerà la matrice numpy

array([[ 4, 10, 12,  6],
       [ 8,  4,  3,  5],
       [14,  2, 12, 18],
       [ 5,  2,  4,  1],
       [12,  6,  8,  6]])
Mostra soluzione
[66]:
import numpy as np

def radalt(mat):
    raise Exception('TODO IMPLEMENT ME !')

# INIZIO TEST: NON TOCCARE !

m1 = np.array([ [2] ])
r1 = np.array([ [4] ])
assert np.allclose(radalt(m1),r1)
assert m1[0][0] == 2  # controlla non si stia modificando la matrice originale

m2 = np.array( [ [ 2, 5, 6],
                 [ 8, 4, 3]])
r2 = np.array( [ [ 4,10,12],
                 [ 8, 4, 3]])
assert np.allclose(radalt(m2), r2)

m3 = np.array( [ [ 2, 5, 6, 3],
                 [ 8, 4, 3, 5],
                 [ 7, 1, 6, 9],
                 [ 5, 2, 4, 1],
                 [ 6, 3, 4, 3] ])
r3 = np.array( [ [ 4,10,12, 6],
                 [ 8, 4, 3, 5],
                 [14, 2,12,18],
                 [ 5, 2, 4, 1],
                 [12, 6, 8, 6] ])
assert np.allclose(radalt(m3), r3)
# FINE TEST

scacchiera

✪✪✪ RITORNA una NUOVA matrice numpy di n righe e n colonne, in cui le celle si alternano tra zero e uno.

Esempio: scacchiera(4) deve dare:

array([[1.0, 0.0, 1.0, 0.0],
       [0.0, 1.0, 0.0, 1.0],
       [1.0, 0.0, 1.0, 0.0],
       [0.0, 1.0, 0.0, 1.0]])

Ingredienti:

  • per alternare, potete usare la range nella forma in cui prende 3 parametri, per esempio range(0,n,2) parte da 0, arriva fino a n escluso e salta di due in due, generando 0,2,4,6,8, …

  • invece range(1,n,2) genererebbe 1,3,5,7, …

Mostra soluzione
[67]:
def scacchiera(n):
    raise Exception('TODO IMPLEMENT ME !')

r1 = np.array( [ [1.0, 0.0, 1.0, 0.0],
                 [0.0, 1.0, 0.0, 1.0],
                 [1.0, 0.0, 1.0, 0.0],
                 [0.0, 1.0, 0.0, 1.0] ])

# all_close ritorna True se tutti i valori nella prima matrice sono abbastanza vicini
# (cioè entro una certa tolleranza) ai corrispondenti nella seconda
assert np.allclose(scacchiera(4), r1)

r2 = np.array( [ [1.0] ])
assert np.allclose(scacchiera(1), r2)

r3 = np.array( [ [1.0, 0.0],
                 [0.0, 1.0] ])
assert np.allclose(scacchiera(2), r3)

somma_alterna

✪✪✪ MODIFICA la matrice numpy in input (n x n), sommando a tutte le righe dispari le righe pari. Per esempio:

m = [[1.0, 3.0, 2.0, 5.0],
     [2.0, 8.0, 5.0, 9.0],
     [6.0, 9.0, 7.0, 2.0],
     [4.0, 7.0, 2.0, 4.0]]
somma_alterna(m)

adesso m dovrebbe essere :

m = [[1.0, 3.0, 2.0, 5.0],
     [3.0, 11.0,7.0, 14.0],
     [6.0, 9.0, 7.0, 2.0],
     [10.0,16.0,9.0, 6.0]]

Ingredienti:

  • per alternare, potete usare la range nella forma in cui prende 3 parametri, per esempio range(0,n,2) parte da 0, arriva fino a n escluso e salta di due in due, generando 0,2,4,6,8, …

  • invece range(1,n,2) genererebbe 1,3,5,7, …

Mostra soluzione
[68]:
def somma_alterna(mat):
    """ MODIFICA la matrice numpy in input (n x n), sommando a tutte le righe dispari le righe pari.
    """
    raise Exception('TODO IMPLEMENT ME !')


m1 = np.array( [ [1.0, 3.0, 2.0, 5.0],
                       [2.0, 8.0, 5.0, 9.0],
                       [6.0, 9.0, 7.0, 2.0],
                       [4.0, 7.0, 2.0, 4.0] ])

r1 = np.array( [ [1.0, 3.0, 2.0, 5.0],
                 [3.0, 11.0,7.0, 14.0],
                 [6.0, 9.0, 7.0, 2.0],
                 [10.0,16.0,9.0, 6.0] ])

somma_alterna(m1)
assert np.allclose(m1, r1)  # controlla che abbiamo MODIFICATO la matrice originale

m2 = np.array( [ [5.0] ])
r2 = np.array( [ [5.0] ])
somma_alterna(m1)
assert np.allclose(m2, r2)

m3 = np.array( [ [6.0, 1.0],
                 [3.0, 2.0] ])
r3 = np.array( [ [6.0, 1.0],
                 [9.0, 3.0] ])
somma_alterna(m3)
assert np.allclose(m3, r3)

media_meta

✪✪✪ Prende in input una matrice numpy con un numero pari di colonne, e RITORNA in output una matrice numpy 1x2: il primo elemento sarà la media della metà sinistra della matrice, il secondo elemento sarà la media della metà destra.

Ingredienti:

  • per ottenere il numero di colonne diviso 2 come numero intero, usare l’operatore //

Mostra soluzione
[69]:
def media_meta(mat):
    raise Exception('TODO IMPLEMENT ME !')

# INIZIO TEST
m1 = np.array([[3,2,1,4],
              [6,2,3,5],
              [4,3,6,2],
              [4,6,5,4],
              [7,2,9,3]])

r1 = np.array([(3+2+6+2+4+3+4+6+7+2)/10, (1+4+3+5+6+2+5+4+9+3)/10  ])

assert np.allclose( media_meta(m1), r1)
# FINE TEST

matxarr

✪✪✪ Prende una matrice numpy n x m e un ndarray di m elementi, e RITORNA una NUOVA matrice numpy in cui i valori di ogni colonna della matrice di input sono moltiplicati per il corrispondente valore dell’array di n elementi.

Mostra soluzione
[70]:
def matxarr(mat, arr):
    raise Exception('TODO IMPLEMENT ME !')

m1 = np.array( [ [3,2,1],
                 [6,2,3],
                 [4,3,6],
                 [4,6,5] ] )
a1 = [5, 2, 6]
r1 = [ [3*5, 2*2, 1*6],
       [6*5, 2*2, 3*6],
       [4*5, 3*2, 6*6],
       [4*5, 6*2, 5*6] ]
assert np.allclose(matxarr(m1,a1), r1)

quadranti

✪✪✪ Data una matrice 2n * 2n, dividere la matrice in 4 parti quadrate uguali (vedi esempio per capire meglio) e RESTITUIRE una NUOVA matrice 2 * 2 contente la media di ogni quadrante

Si assume che la matrice sia sempre di dimensioni pari

SUGGERIMENTO: per dividere per 2 e ottenere un numero intero, usare l’operatore //

Esempio:

1, 2 , 5 , 7
4, 1 , 8 , 0
2, 0 , 5 , 1
0, 2 , 1 , 1

si divide in

  1, 2 | 5 , 7
  4, 1 | 8 , 0
-----------------
  2, 0 | 5 , 1
  0, 2 | 1 , 1

e si restituisce

(1+2+4+1)/ 4  | (5+7+8+0)/4                        2.0 , 5.0
-----------------------------            =>        1.0 , 2.0
(2+0+0+2)/4   | (5+1+1+1)/4
Mostra soluzione
[71]:
import numpy as np

def quadranti(matrice):
    raise Exception('TODO IMPLEMENT ME !')


# INIZIO TEST - NON TOCCARE !
m1 = np.array( [ [3.0, 5.0],
                 [4.0, 9.0] ])
r1 = np.array([  [3.0, 5.0],
                 [4.0, 9.0],
              ])
assert np.allclose(quadranti(m1),r1)

m2 = np.array( [ [1.0, 2.0 , 5.0 , 7.0],
                 [4.0, 1.0 , 8.0 , 0.0],
                 [2.0, 0.0 , 5.0 , 1.0],
                 [0.0, 2.0 , 1.0 , 1.0] ])
r2 = np.array( [ [2.0, 5.0],
                 [1.0, 2.0] ] )
assert np.allclose(quadranti(m2),r2)
# FINE TEST

Altri esercizi numpy

  • Prova a svolgere gli esercizi delle liste di liste, ma usando invece Numpy.

  • Leggi i tutorial Nicola Zoppetti, parte Numpy e prova a rendere gli esercizi già visti più efficienti sostituendo ai cicli for delle funzioni specifiche di numpy che operano su vettori

  • (in inglese) machinelearningplus Esercizi su Numpy (Fermarsi a difficoltà L1, L2 e se vuoi prova L3)

[ ]: