La lliçó anterior va acabar assenyalant el culpable: les llistes de Python no saben de matemàtiques. [12.50, 9.95] * 2 no duplica els preus — repeteix la llista. Per sumar l'IVA a 487 quanties necessites un bucle o una comprensió, i per a un milió de quanties aquell bucle es nota. NumPy (Numerical Python) resol totes dues coses amb una sola estructura, el ndarray: un bloc compacte de números del mateix tipus sobre el qual les operacions s'escriuen sense bucle i s'executen en C. És la peça més avall de l'ecosistema PyData — pandas, Matplotlib i scikit-learn estan construïts a sobre — així que entendre NumPy és entendre què fan els altres per dins.
Contingut
- Per què les llistes no basten
- El
ndarray:np.array,dtype,shape,ndim - Vectorització: operar sense bucles
- Crear arrays:
zeros,ones,arange,linspace - Indexació, slicing i la idea estrella: el boolean masking
- Agregacions:
sum,mean,max,argmax,std - Arrays 2D i el paràmetre
axis - Broadcasting bàsic
- Llista vs array: quan cadascun
Per què les llistes no basten
Instal·la i prova (dins del venv, com sempre):
preus = [12.50, 9.95, 15.90, 21.00] # Odissea, Hamlet, Quixot, Faust
print(preus * 2) # duplicar preus? No: repetir la llista
# [12.5, 9.95, 15.9, 21.0, 12.5, 9.95, 15.9, 21.0]
# print(preus * 1.04) # TypeError: can't multiply sequence by non-intLa llista és un contenidor genèric: pot barrejar floats, cadenes i dataclasses Llibre, i per això cada element és un objecte Python complet amb el seu sobrecost. Dues conseqüències:
- Expressivitat: les operacions matemàtiques element a element no existeixen; cal escriure el bucle (o la comprensió de M2) cada vegada.
- Rendiment: el bucle de Python interpreta cada iteració; amb moltes dades, la diferència respecte al codi compilat és d'un a dos ordres de magnitud.
El ndarray
import numpy as np # l'alies 'np' es una convencio universal
preus = np.array([12.50, 9.95, 15.90, 21.00])
print(preus) # [12.5 9.95 15.9 21. ]
print(type(preus)) # <class 'numpy.ndarray'>
print(preus.dtype) # float64 -> TOTS els elements son d'aquest tipus
print(preus.shape) # (4,) -> tupla amb la mida de cada dimensio
print(preus.ndim) # 1 -> nombre de dimensionsTres atributs que consultaràs constantment:
| Atribut | Què diu | Exemple |
|---|---|---|
dtype |
Tipus únic de tots els elements (float64, int64, bool...) |
float64 |
shape |
Mida per dimensió, com a tupla | (4,) vector, (4, 6) matriu |
ndim |
Nombre de dimensions | 1 vector, 2 taula |
El preu d'aquesta homogeneïtat: si barreges tipus, NumPy ho converteix tot al més general (np.array([1, 2.5]) → float64; si hi ha una cadena, tot acaba sent text i adeu matemàtiques). Aquesta restricció és justament el que li permet ser compacte i ràpid.
Vectorització: operar sense bucles
L'operació s'aplica element a element, sense que escriguis el bucle. Compara les tres maneres d'afegir l'IVA del 4% als preus:
preus_llista = [12.50, 9.95, 15.90, 21.00]
IVA_LLIBRES = 0.04
# 1) Bucle classic (M2)
amb_iva = []
for p in preus_llista:
amb_iva.append(round(p * (1 + IVA_LLIBRES), 2))
# 2) Comprensio (M2)
amb_iva = [round(p * (1 + IVA_LLIBRES), 2) for p in preus_llista]
# 3) NumPy: l'operacio ES l'expressio
preus = np.array(preus_llista)
amb_iva = np.round(preus * (1 + IVA_LLIBRES), 2)
print(amb_iva) # [13. 10.35 16.54 21.84]Les tres donen el mateix; la tercera no només és més curta, és més ràpida (el bucle passa en C) i es llegeix com matemàtiques: "preus per 1.04". El mateix amb dos arrays:
unitats = np.array([154, 121, 182, 63]) # venudes gen-juny (11-01)
ingressos = preus * unitats # element a element
print(ingressos) # [1925. 1203.95 2893.8 1323. ]
print(ingressos.sum()) # 7345.75 -> facturacio total a PVPI la tarifa de soci de tot el curs (IVA del 4 % i descompte del 5 %, la fórmula de M3), ara en una línia:
IVA_LLIBRES, DESCOMPTE_SOCI = 0.04, 0.05
preus_soci = np.round(preus * (1 + IVA_LLIBRES) * (1 - DESCOMPTE_SOCI), 2)
print(preus_soci) # [12.35 9.83 15.71 20.75] <- els de M3, clavatsCrear arrays
| Funció | Què crea | Exemple |
|---|---|---|
np.zeros(n) |
n zeros (acumuladors) | np.zeros(7) → comptador per dia de la setmana |
np.ones(n) |
n uns | np.ones(4) |
np.arange(a, b, pas) |
Com range, però array (admet floats) |
np.arange(1, 7) → mesos [1 2 3 4 5 6] |
np.linspace(a, b, n) |
n punts equiespaiats incloent-hi tots dos extrems | np.linspace(0, 1, 5) → [0. 0.25 0.5 0.75 1.] |
mesos = np.arange(1, 7)
print(mesos) # [1 2 3 4 5 6]
print(np.zeros(3)) # [0. 0. 0.]
print(np.linspace(9.95, 21.0, 3)) # [ 9.95 15.475 21. ]Indexació, slicing i boolean masking
Índexs i llesques funcionen com a les llistes (M4):
quanties = np.array([12.50, 19.90, 15.71, 21.00, 31.80, 47.70, 25.00])
print(quanties[0]) # 12.5
print(quanties[-1]) # 25.0
print(quanties[1:4]) # [19.9 15.71 21. ]I ara la idea estrella de tot l'ecosistema, la que reapareixerà idèntica a pandas: comparar un array produeix un array de booleans, i aquest array serveix de filtre.
grans = quanties > 15.00
print(grans) # [False True True True True True True]
print(quanties[grans]) # [19.9 15.71 21. 31.8 47.7 25. ]
# En una linia, com s'escriu a la practica:
print(quanties[quanties > 15.00])
# Combinant condicions: & (i), | (o), ~ (no) — amb parentesis obligatoris
print(quanties[(quanties > 15) & (quanties < 30)]) # [19.9 15.71 21. 25. ]Llegeix-ho a poc a poc: quanties > 15.00 no és un if, és una operació vectoritzada que retorna (7,) booleans; usar-lo entre claudàtors selecciona els elements on hi ha True. És el filter/comprensió-amb-if de M2 convertit en àlgebra. Un parent útil: np.where(condicio, si_true, si_false) aplica un "if vectoritzat" (np.where(es_soci, preu * 0.95, preu)).
Agregacions
print(quanties.sum()) # 173.61
print(quanties.mean()) # 24.80142857142857
print(quanties.max()) # 47.7
print(quanties.argmax()) # 5 -> l'INDEX del maxim, no el valor
print(quanties.std()) # 11.29... desviacio tipica: com es dispersenargmax mereix atenció: retorna on és el màxim, cosa que permet respondre "quin títol factura més?" creuant arrays paral·lels:
titols = np.array(["L'Odissea", "Hamlet", "El Quixot", "Faust"])
print(titols[ingressos.argmax()]) # El Quixot (2893.80 €)I combinant màscara + agregació, preguntes senceres en una línia: quanties[quanties > 15].mean() — "quantia mitjana de les vendes grans".
Arrays 2D i el paràmetre axis
Les vendes per títol i per mes formen una taula: 4 files (títols) × 6 columnes (gener-juny). Conceptualment, les unitats de vendes_2026.csv reorganitzades:
# files: Odissea, Hamlet, Quixot, Faust | columnes: gen..juny
unitats_2d = np.array([
[18, 17, 21, 50, 25, 23], # L'Odissea
[15, 13, 17, 39, 19, 18], # Hamlet
[22, 20, 25, 59, 29, 27], # El Quixot
[ 7, 8, 8, 20, 11, 9], # Faust
])
print(unitats_2d.shape) # (4, 6)
print(unitats_2d.ndim) # 2
print(unitats_2d[2, 3]) # 59 -> El Quixot a l'abril (fila 2, columna 3)
print(unitats_2d[:, 3]) # [50 39 59 20] -> TOTS els titols a l'abrilaxis indica al llarg de quina dimensió s'agrega — la dimensió que desapareix:
print(unitats_2d.sum()) # 520 -> total del semestre
print(unitats_2d.sum(axis=0)) # [ 62 58 71 168 84 77] -> per MES (aplana les files)
print(unitats_2d.sum(axis=1)) # [154 121 182 63] -> per TITOL (aplana les columnes)Aquí el tens, en números, l'ham de M10: l'abril (168) més que duplica qualsevol altre mes. Sant Jordi existeix i els arrays el veuen. La regla mnemotècnica: axis=0 agrega "cap avall" (resultat per columna), axis=1 "cap a la dreta" (resultat per fila).
Broadcasting bàsic
El broadcasting és la regla per la qual NumPy opera arrays de formes diferents "estirant" el petit. Dos casos cobreixen el 90% de l'ús:
# 1) Escalar contra array: l'escalar s'aplica a tot (ja ho has usat: preus * 1.04)
print(unitats_2d * 2)
# 2) Vector contra matriu: el vector (6,) s'aplica a CADA fila de la (4, 6)
pes_mes = np.array([1.0, 1.0, 1.0, 0.5, 1.0, 1.0]) # abril "descomptat" en comparar
print((unitats_2d * pes_mes).sum(axis=1))
# [129. 101.5 152.5 53. ] -> vendes "desestacionalitzades" per titolLa regla formal (les formes es comparen de dreta a esquerra i han de coincidir o ser 1) dona per a molt més, però amb escalar-contra-array i vector-contra-matriu tens el que et cal per a aquest curs.
Llista vs array: quan cadascun
| Criteri | list |
np.ndarray |
|---|---|---|
Tipus barrejats (Llibre, cadenes, floats) |
Sí — la seva gran virtut | No: un sol dtype |
Créixer element a element (append) |
Barata i natural | Cara (recrea l'array): construeix la llista i converteix al final |
| Matemàtiques element a element | Bucle/comprensió manual | Una expressió, executada en C |
| Filtrar per condició | Comprensió amb if |
Màscara booleana |
| Milers/milions de números | Lenta i pesada en memòria | El seu hàbitat natural |
| El catàleg de Papyrus (objectes rics) | list[Llibre] de M5, correcte |
No aplica |
L'honestedat promesa: en el dia a dia de Papyrus gairebé no escriuràs NumPy pur — faràs servir pandas, que posa noms a files i columnes i llegeix CSV tot sol. Però pandas és NumPy per sota: cada columna d'un DataFrame és un array, les màscares booleanes d'aquesta lliçó s'hi escriuen idèntiques, i axis, dtype i les agregacions hi reapareixen amb els mateixos noms. El que has après aquí no es llença: s'hereta.
Errors Comuns i Consells
and/oren màscares.(a > 1) and (a < 5)llançaValueError: The truth value of an array is ambiguous. Amb arrays s'usen&,|,~— i sempre amb parèntesis, perquè&té més precedència que>.- Oblidar que l'slice és una vista.
b = a[1:4]; b[0] = 99modifica tambéa(a diferència de les llistes de M4, on l'slicing copia). Si necessites independència:a[1:4].copy(). dtypesilenciosament enter.np.array([12, 9, 15])ésint64; una divisió posterior funciona (produeix floats), però assignara[0] = 12.5truncaria a12. Si treballes amb preus, parteix de floats:np.array([12.0, ...]).- Confondre
maxiargmax. L'un retorna el valor, l'altre l'índex. Per a "quin títol ven més?" necessitesargmax+ l'array de títols. - Fer
appenden bucle a un array.np.appendrecrea l'array sencer cada vegada. Acumula en unalisti fesnp.array(llista)al final — o millor, deixa quepandas.read_csvho faci tot a 11-03.
Exercicis
- Amb
preus = np.array([12.50, 9.95, 15.90, 21.00])iunitats = np.array([154, 121, 182, 63]): calcula en una expressió cadascun els ingressos per títol a tarifa de soci (IVA 4 % + descompte 5 %, arrodoniment a 2 decimals del preu abans de multiplicar) i el títol amb menys ingressos a PVP. - Amb
quanties = np.array([12.50, 19.90, 15.71, 21.00, 31.80, 47.70, 25.00, 9.95, 12.35]): (a) quantes vendes superen la mitjana? (b) quin percentatge de la quantia total aporten? Pistes: una màscara es pot sumar (Trueval 1) i usar-se dues vegades. - Amb la matriu
unitats_2dde la lliçó: obtén la mitjana mensual de cada títol excloent l'abril (per no deixar que Sant Jordi infli el "mes normal"), fent servir slicing o una màscara sobre les columnes. Quin títol té el mes normal més alt?
Solucions
-
Compte amb el matís de l'enunciat: s'arrodoneix la tarifa de soci (12.35, 9.83...) i després es multiplica — tal com cobra Papyrus — no a l'inrevés.ingressos_soci = np.round(preus * 1.04 * 0.95, 2) * unitats print(ingressos_soci) # [1901.9 1189.43 2859.22 1307.25] titols = np.array(["L'Odissea", "Hamlet", "El Quixot", "Faust"]) print(titols[(preus * unitats).argmin()]) # Hamlet (1203.95 € a PVP) -
Tres vendes de nou aporten més de la meitat de la quantia: un altre cop l'asimetria de 11-01.mitjana = quanties.mean() # 21.77... mascara = quanties > mitjana print(mascara.sum()) # 3 vendes (31.80, 47.70, 25.00) print(quanties[mascara].sum() / quanties.sum() * 100) # 53.2...%mascara.sum()compta elsTrueperquè els booleans són 0/1 — un idiotisme de NumPy que veuràs constantment. -
sense_abril = np.delete(unitats_2d, 3, axis=1) # o unitats_2d[:, [0,1,2,4,5]] mitjanes = sense_abril.mean(axis=1) print(np.round(mitjanes, 1)) # [20.8 16.4 24.6 8.6] titols = np.array(["L'Odissea", "Hamlet", "El Quixot", "Faust"]) print(titols[mitjanes.argmax()]) # El Quixotaxis=1perquè vols aplanar els mesos (columnes) i quedar-te amb un valor per títol (fila). El Quixot lidera també el mes "normal": el seu lideratge no és només cosa de Sant Jordi.
Conclusió
NumPy t'ha donat el motor: el ndarray homogeni i compacte, la vectorització que converteix bucles en expressions (preus * 1.04), les màscares booleanes que converteixen filtres en àlgebra (quanties[quanties > 15]), les agregacions amb axis que han fet visible el pic de Sant Jordi (abril: 168 unitats), i el broadcasting que estira escalars i vectors sense copiar-los. També t'ha ensenyat el seu límit per a Papyrus: els arrays són números anònims — la fila 2 "és" El Quixot només perquè tu ho recordes, i ficar les 487 files del CSV en arrays paral·lels a mà és feina de picapedrer. La propera lliçó presenta pandas, que embolica aquests arrays en taules amb noms: columnes etiquetades, read_csv que carrega vendes_2026.csv en una línia (el csv.DictReader de M6 amb esteroides, com havíem promès), i groupby per respondre per fi, amb dades i d'una vegada, les preguntes de l'Anna.
Curs de Programació en Python
Mòdul 1: Introducció a Python
- Introducció a Python
- Configuració de l'Entorn de Desenvolupament
- Sintaxi de Python i Tipus de Dades Bàsics
- Variables i Constants
- Entrada i Sortida Bàsica
- Entorns Virtuals i Gestió de Paquets
Mòdul 2: Estructures de Control
Mòdul 3: Funcions i Mòduls
- Definició de Funcions
- Arguments de Funció
- Funcions Lambda
- Mòduls i Paquets
- Visió General de la Biblioteca Estàndard
Mòdul 4: Estructures de Dades
Mòdul 5: Programació Orientada a Objectes
Mòdul 6: Gestió de Fitxers
- Lectura i Escriptura de Fitxers
- Treball amb Fitxers CSV
- Gestió de Dades JSON
- Operacions amb Fitxers i Directoris
Mòdul 7: Gestió d'Errors i Excepcions
- Introducció a les Excepcions
- Gestió d'Excepcions
- Llançament d'Excepcions
- Excepcions Personalitzades
- Bones Pràctiques i Registre d'Errors amb logging
Mòdul 8: Temes Avançats
- Anotacions de Tipus (type hints)
- Decoradors
- Generadors
- Gestors de Context
- Concurrència: Fils i Processos
- Asyncio per a la Programació Asíncrona
Mòdul 9: Proves i Depuració
- Introducció a les Proves
- Proves Unitàries amb unittest
- Proves amb pytest
- Desenvolupament Guiat per Proves
- Tècniques de Depuració
- Ús de pdb per a la Depuració
Mòdul 10: Desenvolupament Web amb Python
- Introducció al Desenvolupament Web
- Fonaments del Framework Flask
- Construcció d'APIs REST amb Flask
- Introducció a Django
- Construcció d'Aplicacions Web amb Django
Mòdul 11: Ciència de Dades amb Python
- Introducció a la Ciència de Dades
- NumPy per a la Computació Numèrica
- Pandas per a la Manipulació de Dades
- Matplotlib per a la Visualització de Dades
- Introducció a l'Aprenentatge Automàtic amb scikit-learn
