NumPy
- Creació i inspecció d’arrays
- Indexació i llescat
- Broadcasting i funcions universals
- Agregacions per eix
- Màscares booleanes
- Àlgebra lineal i operacions amb matrius
- Canvi de forma, indexació avançada i mostreig aleatori
Creació i inspecció d’arrays
Com es creen els arrays de NumPy i com se’n consulten les propietats. Un array és una graella de nombres, tots del mateix tipus, amb un o més eixos.
import numpy as np
np.random.seed(0)
Creació d’arrays
np.arraya partir d’una llista (una llista de llistes dona un array 2D, una llista interior per fila)np.zeros,np.ones,np.full,np.eye- Intervals:
np.arange(inici, fi, pas)excloufi;np.linspace(inici, fi, quants)l’inclou - Arrays aleatoris:
np.random.rand,np.random.randn
a = np.array([1, 2, 3])
b = np.zeros((2,3))
c = np.ones((3,3))
d = np.full((2,2), 7)
e = np.eye(4)
rand = np.random.rand(2,3)
print(a, b, c, d, e, rand, sep='\n')
[1 2 3]
[[0. 0. 0.]
[0. 0. 0.]]
[[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]]
[[7 7]
[7 7]]
[[1. 0. 0. 0.]
[0. 1. 0. 0.]
[0. 0. 1. 0.]
[0. 0. 0. 1.]]
[[0.5488135 0.71518937 0.60276338]
[0.54488318 0.4236548 0.64589411]]
print(np.arange(0, 1, 0.25)) # pas de 0.25; l'1 queda fora
print(np.linspace(0, 1, 5)) # 5 valors igualment espaiats; l'1 hi és
[0. 0.25 0.5 0.75]
[0. 0.25 0.5 0.75 1. ]
Inspecció d’un array
shape: la mida de cada eix, en una tupla.(4,)és una fila de 4 valors;(4, 3)és una taula de 4 files i 3 columnesndim: el nombre d’eixos;len(): la mida del primer eixdtype: el tipus de tots els elements (un array només en té un)
proba = np.array([[0.8, 0.1, 0.1],
[0.1, 0.6, 0.3],
[0.0, 0.3, 0.7],
[0.2, 0.5, 0.3]])
print('shape:', proba.shape, '| ndim:', proba.ndim, '| len:', len(proba), '| dtype:', proba.dtype)
shape: (4, 3) | ndim: 2 | len: 4 | dtype: float64
Quan el codi fa una cosa inesperada, imprimeix primer el shape: la majoria d’errors amb
NumPy són un array amb una forma diferent de la que et pensaves.
Tipus de dades i conversió
print(a.dtype)
a_float = a.astype(float)
print(a_float.dtype)
int64
float64
astype(int) converteix els booleans en 0 i 1. És així com una decisió de sí o no es
converteix en una etiqueta de classe:
print(np.array([True, False, True]).astype(int))
[1 0 1]
Avançat: arrays estructurats
dt = np.dtype([('name','U10'), ('age','i4'), ('weight','f4')])
people = np.array([('Alice', 25, 55.0), ('Bob', 30, 85.5)], dtype=dt)
print(people['name'], people['age'], people['weight'])
['Alice' 'Bob'] [25 30] [55. 85.5]
Indexació i llescat
Com s’obtenen valors sols, intervals, files i columnes d’un array.
Una dimensió
a[i]: un element; els índexs negatius compten des del finala[inici:fi]: un tros (slice), sensefi; qualsevol dels dos extrems es pot ometre
a = np.array([10, 20, 30, 40, 50])
print(a[0], a[-1]) # el primer i l'últim
print(a[:3]) # els tres primers
print(a[3:]) # de l'índex 3 fins al final
10 50
[10 20 30]
[40 50]
Dues dimensions: files i columnes
m[fila, columna]: cada part és un índex, un tros o:, que vol dir totesm[i]és la filai, amb totes les columnes; una columna necessitam[:, j]
m = np.array([[0.8, 0.1, 0.1],
[0.1, 0.6, 0.3],
[0.0, 0.3, 0.7],
[0.2, 0.5, 0.3]])
print('m[2, 2] =', m[2, 2]) # fila 2, columna 2: un sol valor
print('m[1] =', m[1]) # la fila 1
print('m[:, 1] =', m[:, 1]) # la columna 1, de totes les files
print('m[:2] =\n', m[:2]) # les dues primeres files
print('m[:, 1:] =\n', m[:, 1:]) # totes les files, de la columna 1 en endavant
m[2, 2] = 0.7
m[1] = [0.1 0.6 0.3]
m[:, 1] = [0.1 0.6 0.3 0.5]
m[:2] =
[[0.8 0.1 0.1]
[0.1 0.6 0.3]]
m[:, 1:] =
[[0.1 0.1]
[0.6 0.3]
[0.3 0.7]
[0.5 0.3]]
Un ús típic: predict_proba torna una fila per mostra i una columna per classe, de manera
que proba[:, 1] és la probabilitat de la classe 1 per a cada mostra.
Vistes i còpies
Un tros d’una llista és una còpia, però un tros d’un array és una vista: comparteix la
memòria amb l’original, i si la canvies, canvies l’original. Crida .copy() quan vulguis
un array independent.
a = np.array([1, 2, 3, 4, 5])
v = a[1:4] # vista
v[0] = 99
print(a) # el 99 també és a a
c = a[1:4].copy() # còpia
c[0] = 0
print(a) # a no canvia
[ 1 99 3 4 5]
[ 1 99 3 4 5]
Broadcasting i funcions universals
Com opera NumPy amb arrays de formes diferents (broadcasting, difusió) i com aprofita les seves funcions universals (ufuncs), que són ràpides.
import numpy as np
np.random.seed(1)
# Arrays d'exemple
a = np.arange(6).reshape(2,3)
b = np.array([1, 2, 3])
print('a:\n', a)
print('b:', b)
a:
[[0 1 2]
[3 4 5]]
b: [1 2 3]
Aritmètica element a element
Una operació entre un array i un nombre, o entre dos arrays de la mateixa forma, es fa element a element, tota de cop. No cal cap bucle, i és molt més ràpida que fer-ne un.
y_true = np.array([200.0, 150.0, 320.0, 410.0])
y_pred = np.array([210.0, 140.0, 300.0, 430.0])
err = y_pred - y_true # un error per mostra
print('errors:', err)
print('MAE :', np.abs(err).mean())
print('RMSE :', np.sqrt((err ** 2).mean()))
errors: [ 10. -10. -20. 20.]
MAE : 15.0
RMSE : 15.811388300841896
Quan les formes són diferents, NumPy aplica el broadcasting, a continuació.
Fonaments del broadcasting
- Quan les dimensions no coincideixen, NumPy «estira» l’array petit al llarg dels eixos que li falten.
- Regla: les darreres dimensions han de coincidir o valer 1.
# Exemple de broadcasting
c = a + b # b es repeteix a cada fila
print('a + b =\n', c)
a + b =
[[1 3 5]
[4 6 8]]
Broadcasting amb més dimensions
- Exemple: sumar un array de forma (2,3,1) i un de forma (3,)
x = np.arange(6).reshape(2,3,1)
y = np.array([10, 20, 30])
print('forma de x:', x.shape)
print('forma de y:', y.shape)
z = x + y # l'últim eix de x (mida 1) s'estira fins a 3, i y es repeteix als dos primers
print('forma de z:', z.shape)
print(z)
forma de x: (2, 3, 1)
forma de y: (3,)
forma de z: (2, 3, 3)
[[[10 20 30]
[11 21 31]
[12 22 32]]
[[13 23 33]
[14 24 34]
[15 25 35]]]
Funcions universals (ufuncs)
- Operacions element a element ràpides, implementades en C.
- Exemples:
np.sin,np.exp,np.add,np.multiply.
print('sin(a):\n', np.sin(a))
print('exp(a):\n', np.exp(a))
sin(a):
[[ 0. 0.84147098 0.90929743]
[ 0.14112001 -0.7568025 -0.95892427]]
exp(a):
[[ 1. 2.71828183 7.3890561 ]
[ 20.08553692 54.59815003 148.4131591 ]]
Mètodes de les ufuncs: reduce, accumulate, outer
reduce: aplica la ufunc fins a fer desaparèixer un eixaccumulate: l’aplica de manera acumuladaouter: l’aplica a totes les parelles
print('add.reduce(a):', np.add.reduce(a, axis=1))
print('add.accumulate(a):\n', np.add.accumulate(a, axis=1))
print('multiply.outer([1,2], [3,4]):\n', np.multiply.outer([1,2], [3,4]))
add.reduce(a): [ 3 12]
add.accumulate(a):
[[ 0 1 3]
[ 3 7 12]]
multiply.outer([1,2], [3,4]):
[[3 4]
[6 8]]
Operacions vectoritzades amb cadenes
- El mòdul
np.charofereix operacions amb cadenes vectoritzades.
s = np.array(['apple', 'banana', 'cherry'])
print('Majúscules:', np.char.upper(s))
print('Canvia a per @:', np.char.replace(s, 'a', '@'))
Majúscules: ['APPLE' 'BANANA' 'CHERRY']
Canvia a per @: ['@pple' 'b@n@n@' 'cherry']
Agregacions per eix
Com es resumeix un array amb sum, mean, min, max i std, i com es troben posicions
amb argmax i argmin.
Agregacions de tot l’array
Sense arguments, una agregació redueix tot l’array a un sol nombre.
proba = np.array([[0.8, 0.1, 0.1],
[0.1, 0.6, 0.3],
[0.0, 0.3, 0.7],
[0.2, 0.5, 0.3]])
print('sum:', proba.sum().round(2), '| max:', proba.max(), '| mean:', proba.mean().round(3))
sum: 4.0 | max: 0.8 | mean: 0.333
L’argument axis
Amb axis, l’agregació es fa al llarg d’aquell eix, i aquest eix desapareix del
resultat. Per a un array de forma (4, 3):
| Crida | Recorre | Resultat |
|---|---|---|
proba.sum(axis=0) | les files, de dalt a baix | un valor per columna, forma (3,) |
proba.sum(axis=1) | les columnes, d’esquerra a dreta | un valor per fila, forma (4,) |
print('sum(axis=0):', proba.sum(axis=0).round(2)) # un total per classe (columna)
print('sum(axis=1):', proba.sum(axis=1).round(2)) # un total per mostra (fila): cadascun suma 1
print('max(axis=1):', proba.max(axis=1)) # la probabilitat més alta de cada mostra
sum(axis=0): [1.1 1.5 1.4]
sum(axis=1): [1. 1. 1. 1.]
max(axis=1): [0.8 0.6 0.7 0.5]
En els arrays d’aprenentatge automàtic, les files solen ser mostres i les columnes,
característiques o classes. Per això «un valor per mostra» vol dir axis=1.
argmax i argmin
argmax torna la posició del màxim, no el seu valor. Amb axis=1, és la columna del
valor més alt de cada fila.
print('max :', proba.max(axis=1))
print('argmax:', proba.argmax(axis=1))
max : [0.8 0.6 0.7 0.5]
argmax: [0 1 2 1]
Pràctica: de probabilitats a prediccions
La classe predita és la columna amb la probabilitat més alta. Un array de posicions pot
indexar un altre array (vegeu Indexació avançada), de manera que les
posicions es converteixen en noms de classe en un sol pas. És el que fa el predict d’un
classificador amb la sortida de predict_proba.
classes = np.array(['setosa', 'versicolor', 'virginica'])
pred = proba.argmax(axis=1)
print(classes[pred])
['setosa' 'versicolor' 'virginica' 'versicolor']
Màscares booleanes
Comparar un array dona un array de True i False, un per element: una màscara. Les
màscares serveixen per comptar, seleccionar i canviar elements sense cap bucle.
Les comparacions donen màscares
y_true = np.array([0, 1, 1, 2])
y_pred = np.array([0, 1, 2, 1])
correct = y_pred == y_true
print(correct)
[ True True False False]
Comptar amb màscares
True compta com 1 i False com 0, de manera que .sum() compta les coincidències i
.mean() en dona la proporció.
print('encerts :', correct.sum())
print('exactitud:', correct.mean())
print('errors :', (y_pred != y_true).sum())
encerts : 2
exactitud: 0.5
errors : 2
Seleccionar amb màscares
a[mascara] es queda amb els elements on la màscara val True. Una màscara 1D sobre un
array 2D selecciona files: ha de tenir un valor per fila.
wrong = y_pred != y_true
print(proba[wrong]) # les files de les mostres mal classificades
print(proba[wrong].max(axis=1)) # quant s'hi refiava el model quan ha fallat
[[0. 0.3 0.7]
[0.2 0.5 0.3]]
[0.7 0.5]
En canvi, una màscara amb la mateixa forma que l’array selecciona elements sols, i els torna en un array 1D pla, perquè ja no formen una taula:
print(proba[proba > 0.5])
[0.8 0.6 0.7]
Combinar condicions
Fes servir & (i), | (o) i ~ (no), amb cada condició entre parèntesis. Els and,
or i not de Python no treballen element a element i, amb arrays, donen error.
p = proba[:, 1]
print(p)
print((p > 0.2) & (p < 0.6))
print(~(p > 0.2))
[0.1 0.6 0.3 0.5]
[False False True True]
[ True False False False]
Canviar valors amb una màscara
Assignar a través d’una màscara només canvia aquelles posicions. En canvi,
np.where(mascara, a, b) construeix un array nou: agafa a on la màscara val True i b
a la resta.
x = np.array([3.0, -1.0, 5.0, -2.0])
print(np.where(x < 0, 0, x)) # array nou: x no canvia
x[x < 0] = 0 # canvia el mateix x
print(x)
[3. 0. 5. 0.]
[3. 0. 5. 0.]
Àlgebra lineal i operacions amb matrius
Productes de matrius, descomposicions i altres operacions d’àlgebra lineal.
import numpy as np
from numpy import linalg as LA
np.random.seed(0)
# Matrius d'exemple
A = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
B = np.array([[9, 8, 7], [6, 5, 4], [3, 2, 1]])
print('A:')
print(A)
print('\nB:')
print(B)
A:
[[1 2 3]
[4 5 6]
[7 8 9]]
B:
[[9 8 7]
[6 5 4]
[3 2 1]]
Producte matricial
- L’operador
@onp.dot
C1 = A @ B
C2 = np.dot(A, B)
print('A @ B =')
print(C1)
print('\nnp.dot(A, B) =')
print(C2)
A @ B =
[[ 30 24 18]
[ 84 69 54]
[138 114 90]]
np.dot(A, B) =
[[ 30 24 18]
[ 84 69 54]
[138 114 90]]
Operacions element a element
*,np.multiply,np.add
print('A * B =')
print(A * B)
print('\nnp.multiply(A, B) =')
print(np.multiply(A, B))
A * B =
[[ 9 16 21]
[24 25 24]
[21 16 9]]
np.multiply(A, B) =
[[ 9 16 21]
[24 25 24]
[21 16 9]]
Inversa, determinant i rang
LA.inv,LA.det,LA.matrix_rank
# Una matriu més petita i invertible
M = np.array([[4, 7], [2, 6]])
print('M:', M)
print('Inversa de M:', LA.inv(M))
print('Determinant de M:', LA.det(M))
print('Rang de A:', LA.matrix_rank(A))
M: [[4 7]
[2 6]]
Inversa de M: [[ 0.6 -0.7]
[-0.2 0.4]]
Determinant de M: 10.000000000000002
Rang de A: 2
Valors i vectors propis
LA.eig. Amb aquesta versió de NumPy, el resultat és complex fins i tot quan els valors són reals:+0.jvol dir part imaginària zero.
eigvals, eigvecs = LA.eig(M)
print('Valors propis:', eigvals)
print('Vectors propis:')
print(eigvecs)
Valors propis: [1.12701665+0.j 8.87298335+0.j]
Vectors propis:
[[-0.92511345+0.j -0.82071729+0.j]
[ 0.37969079+0.j -0.57133452+0.j]]
Sistemes lineals
LA.solveresol Ax = b
b = np.array([1, 2])
x = LA.solve(M, b)
print('Solució x de Mx = b:', x)
# Comprovació: Mx ha de donar b
print('M @ x:', M @ x)
Solució x de Mx = b: [-0.8 0.6]
M @ x: [1. 2.]
Pseudoinversa i mínims quadrats
LA.pinv,LA.lstsq
# Un sistema sobredeterminat: més equacions que incògnites
X = np.random.randn(5, 3)
y = np.random.randn(5)
# Solució per mínims quadrats
coef, residuals, rank, s = LA.lstsq(X, y, rcond=None)
print('Coeficients:', coef)
print('Residus:', residuals)
# La mateixa solució amb la pseudoinversa
coef_pinv = LA.pinv(X) @ y
print('Coeficients amb pinv:', coef_pinv)
Coeficients: [-0.22776487 1.10638653 0.07697564]
Residus: [0.82230318]
Coeficients amb pinv: [-0.22776487 1.10638653 0.07697564]
Producte tensorial i traça
np.trace,np.tensordot
print('Traça de A:', np.trace(A))
print('Producte tensorial de A i B sobre els eixos (1,0):')
print(np.tensordot(A, B, axes=(1, 0)))
Traça de A: 15
Producte tensorial de A i B sobre els eixos (1,0):
[[ 30 24 18]
[ 84 69 54]
[138 114 90]]
Canvi de forma, indexació avançada i mostreig aleatori
Com es canvia la forma d’un array, com s’indexa amb altres arrays i com es generen mostres aleatòries per als experiments.
import numpy as np
np.random.seed(2)
arr = np.arange(24)
print('Forma original de arr:', arr.shape)
Forma original de arr: (24,)
Canviar la forma
reshape(nova_forma)ravel(),flatten()transpose(),swapaxes()
print('reshape a (4,6):')
print(arr.reshape(4,6))
print('\nreshape a (2,3,4):')
print(arr.reshape(2,3,4))
reshape a (4,6):
[[ 0 1 2 3 4 5]
[ 6 7 8 9 10 11]
[12 13 14 15 16 17]
[18 19 20 21 22 23]]
reshape a (2,3,4):
[[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]]
Una característica com a columna
scikit-learn vol X amb dos eixos, (mostres, característiques), encara que només hi hagi
una característica. reshape(-1, 1) converteix un array 1D en una columna; -1 vol dir
«tantes com calguin».
size = np.array([50.0, 80.0, 120.0])
X = size.reshape(-1, 1)
print(size.shape, '->', X.shape)
print(X)
(3,) -> (3, 1)
[[ 50.]
[ 80.]
[120.]]
flatten i ravel
flatten(): torna una còpiaravel(): torna una vista sempre que pot
m = np.arange(6).reshape(2, 3)
flat = m.flatten()
rv = m.ravel()
flat[0] = 100 # còpia: m no canvia
rv[1] = 200 # vista: m canvia
print('m després de modificar flat i rv:')
print(m)
m després de modificar flat i rv:
[[ 0 200 2]
[ 3 4 5]]
Indexació avançada
- Indexació amb un array d’enters: un array de posicions selecciona aquells elements, en aquell ordre
- Màscares booleanes: vegeu Màscares booleanes
take()iput()
# Indexació amb enters
idx = np.array([0, 2, 5])
print('Elements seleccionats:', arr[idx])
# Màscara booleana
mask = arr % 2 == 0
print('Elements parells:', arr[mask])
Elements seleccionats: [0 2 5]
Elements parells: [ 0 2 4 6 8 10 12 14 16 18 20 22]
Afegir i treure dimensions
newaxisexpand_dims(),squeeze()
a = np.arange(6)
print('forma de a:', a.shape)
a2 = a[np.newaxis, :]
print('a amb un eix nou:', a2.shape)
a3 = np.expand_dims(a, axis=1)
print('expand_dims axis=1:', a3.shape)
print('amb squeeze, de nou:', np.squeeze(a3).shape)
forma de a: (6,)
a amb un eix nou: (1, 6)
expand_dims axis=1: (6, 1)
amb squeeze, de nou: (6,)
Mostreig aleatori
rand,randn,randint,random_samplechoice,shuffle
print('rand 2x3:', np.random.rand(2,3))
print('randn 3x3:', np.random.randn(3,3))
print('randint 0-10:', np.random.randint(0, 10, size=5))
print('random_sample 5:', np.random.random_sample(5))
rand 2x3: [[0.4359949 0.02592623 0.54966248]
[0.43532239 0.4203678 0.33033482]]
randn 3x3: [[ 0.50288142 -1.24528809 -1.05795222]
[-0.90900761 0.55145404 2.29220801]
[ 0.04153939 -1.11792545 0.53905832]]
randint 0-10: [1 3 5 8 4]
random_sample 5: [0.29701836 0.28786882 0.11619332 0.18172704 0.49428977]
Permutacions i selecció
shufflebarreja l’array mateixchoicetreu una mostra, amb reposició o sense
arr2 = np.arange(10)
np.random.shuffle(arr2)
print('Barrejat:', arr2)
print('choice, 5 elements:', np.random.choice(arr2, size=5, replace=False))
Barrejat: [5 3 8 6 7 0 1 9 4 2]
choice, 5 elements: [0 7 6 1 8]
Pràctica: partició train/test
El mostreig aleatori serveix per partir les dades en entrenament i prova.
X = np.arange(20).reshape(10,2)
y = np.arange(10)
# Barreja els índexs
indices = np.arange(10)
np.random.shuffle(indices)
# Partició 80/20
split = int(0.8 * len(indices))
train_idx = indices[:split]
test_idx = indices[split:]
X_train, X_test = X[train_idx], X[test_idx]
y_train, y_test = y[train_idx], y[test_idx]
print('X de train:', X_train)
print('X de test:', X_test)
X de train: [[ 2 3]
[12 13]
[10 11]
[ 8 9]
[ 4 5]
[14 15]
[16 17]
[ 0 1]]
X de test: [[ 6 7]
[18 19]]