Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

NumPy

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.array a 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) exclou fi; 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 columnes
  • ndim: el nombre d’eixos; len(): la mida del primer eix
  • dtype: 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 final
  • a[inici:fi]: un tros (slice), sense fi; 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 totes
  • m[i] és la fila i, amb totes les columnes; una columna necessita m[:, 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 eix
  • accumulate: l’aplica de manera acumulada
  • outer: 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.char ofereix 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):

CridaRecorreResultat
proba.sum(axis=0)les files, de dalt a baixun valor per columna, forma (3,)
proba.sum(axis=1)les columnes, d’esquerra a dretaun 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 @ o np.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.j vol 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.solve resol 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òpia
  • ravel(): 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() i put()
# 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

  • newaxis
  • expand_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_sample
  • choice, 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ó

  • shuffle barreja l’array mateix
  • choice treu 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]]
Last change: , commit: 5b81ee3