Matplotlib és una biblioteca que serveix per dibuixar figures, funcions i tot tipus de gràfiques en imatges estàtiques.

Introducció

Matplotlib és una llibreria de baix nivell que serveix per dibuixar figures, funcions i tot tipus de gràfiques estàtiques (en imatges png).

Funciona molt bé amb estructures de Python, arrays de Numpy, sèries i dataFrames de Pandas...

Tot i que hi ha llibreries estàtiques més modernes i senzilles a Python (Seaborn) i altres que ofereixen gràfics animats (Plotly, Bokeh) ens interessa aprendre com funciona Matplotlib, perquè és la que ofereix el màxim nivell de personalització i perquè totes aquestes llibreries tenen un funcionament similar a Matplotlib.

Per aquest motiu, encara hi ha molts treballs científics que presenten les funcions i gràfics amb Matplotlib.

Entorn de treball

Crea un nou projecte:

$ poetry new plot --name app
$ cd plot
$ poetry add matplotlib numpy

Crea el fitxer app/main.py:

import matplotlib.pyplot as plt

xs = [2, 1, 5, 7, 11, 12, 15, 13]

plt.plot(xs)
plt.savefig("plot.png")

Obre el fitxer plot.png.

Pots veure que s'ha generat un gràfic de la seqüència xs.

Crear gràfics de senzills amb el mètode plot()

El gràfic més senzill que podem crear és una línia a partir d'una matriu plana (1D) o una llista.

A l'importar, ja creem un contenidor (un canvas = un lienzo) dins de l'objecte plt.

El mètode plot és el que dibuixa el gràfic dins del contenidor plt.

Si no li diem res més crea un gràfic de punts amb les dades de la llista que li hem passat.

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
l1 = [2, 1, 5, 7, 11, 12, 15, 13]
plt.plot(l1)
plt.show()

També podem crear un gràfic amb diverses línies (una des d'un array 1D i una altra des d'una linia)

import numpy as np
import matplotlib.pyplot as plt
#%matplotlib inline
a1 = np.arange(2,17,2)
l1 = [2, 1, 5, 7, 11, 12, 15, 13]
plt.plot(l1)
plt.plot(a1)
#plt.show()
plt.savefig('matplotlib1.png')

També pots veure com crear una matriu i dibuixar una recta i posar-li punts a damunt.

x = np.linspace(0, 5, 20)
y = np.linspace(0, 10, 20)
plt.plot(x, y, color = 'purple')    # linia
plt.plot(x, y, 'o')         # punts
plt.savefig('matplotlib2.png')

Amb Matplotlib, pots accedir a un enorme ventall d'opcions de visualizació 2D i fins i tot 3D. En aquest cas mostrem una ona sinusoidal radial.

Com a curiositat, aquesta funció es pot descriure matemàticament com:

Z = sin(R)

on

R = sqrt{X^2 + Y^2}

Desglossem-ho una mica més:

  • R és la distància euclideana (equivalent al teorema de Pitagoras) des de l'origen (0, 0) fins a qualsevol punt ((X, Y)) en el pla.
  • La funció sin(R) pren aquesta distància radial i calcula el valor del sinus d'aquesta distància.

Les ones sinusoidals radials són útils per modelar fenòmens que es propaguen radialment des d'un punt central, com ara les ones de calor o electromagnètiques.

Aquest tipus de visualització només la mostrem com a demostració del potencial de la biblioteca.

fig = plt.figure()
ax = fig.add_subplot(projection='3d')
# Vectors de coordenades
X = np.arange(-5, 5, 0.15)
Y = np.arange(-5, 5, 0.15)
# Els transformem a matriu de coordenades.
X, Y = np.meshgrid(X, Y)
# Apliquem les funcions
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap='viridis')
plt.savefig('matplotlib3.png')

Paràmetres bàsics de la funció plot()

Els més importants i comuns en tots els gràfics són:

  • x --> Estructura de dades (llista, diccionari, array, dataframe ...) on es troben les dades a dibuixar. Necessari.
  • y --> Dimensió Y per figures 2D i gràfics de funcions que representin linies (siguin rectes o no) o punts en un pla.
  • z --> Dimensió Z per gràfics 3D.
  • color --> Color dels punts/rectes/figures.
  • label --> posem llegenda a la línia.
  • fmt --> format. Si tenim números decimals

Si no li diem res, la funció infereix els paràmetres (sobretot els x, y que són molt habituals), però és bona pràctica especificar paràmetres.

Apart dels paràmetres de la funció plot, tenim altres mètodes importants dins el contenidor del gràfic (plt en el nostre cas).

Els més bàsics són els que mostren les etiquetes dels eixos i del títol del gràfic.

  • ylabel("Etiqueta eix vertical")
  • xlabel("Etiqueta eix horitzontal")
  • title("Titol gràfic")
  • legend() --> Mostra una llegenda de les funcions o barres
  • grid() --> Mostra una graella per facilitar la visió

I com fer el gràfic més gran o més petit ? Podem usar aquest mètode abans de mostrar o guardar el gràfic, que indica les proporcions:

py plt.figure(figsize=(10,6))

Gràfics de linies.

Usant els arrays de Numpy i llistes, podem crear gràfics de diverses línies.

Per exemple, la evolució de vendes de 3 models de targetes gràfiques en 4 mesos.

import matplotlib.pyplot as plt
import numpy as np

# Dades fictícies de vendes de 3 models de targetes gràfiques en 4 mesos
mesos = ["Gener", "Febrer", "Març", "Abril"]
gpu_models = ["RX 6400 Pulse", "AMD Radeon RX 7900 XTX", "RTX 3050 Dual OC 6 GB"]
vendes_gpu = np.array([
    [120, 150, 170, 200],  # RX 6400 Pulse
    [90, 110, 140, 160],   # AMD Radeon RX 7900 XTX
    [100, 130, 120, 180]   # RTX 3050 Dual OC 6 GB
])

# Crear el gràfic de línies utilitzant l'array de Numpy
# Marcadors i colors per cada línia
markers = ['o', 's', '^', 'D']  
colors = ['blue', 'green', 'red', 'purple']  

# Crear el gràfic de línies amb un bucle
plt.figure(figsize=(10, 6))
for i in range(len(gpu_models)):
    plt.plot(mesos, vendes_gpu[i], marker=markers[i % len(markers)], label=gpu_models[i], linestyle='-', color=colors[i % len(colors)])

# Afegir títol i etiquetes
plt.title('Evolució de Vendes de Targetes Gràfiques (4 mesos)', fontsize=14)
plt.xlabel('Mesos', fontsize=12)
plt.ylabel('Vendes (Unitats)', fontsize=12)
plt.grid(True)
plt.legend(title="Models de GPU")

# Mostrar el gràfic
plt.tight_layout()
plt.savefig("plot.png")

Aquesta solució és correcta i escalable (si afegim mesos o productes funciona igual)

1.

Crea un gràfic amb 3 línies que representin la evolució del seu nivell de glucosa a la sang en mg/L que tenen 3 pacients amb grip i amb 7 columnes, una per dia; guardat en numpy.

Pots inserir manualment les mostres o bé generar-les aleatòriament; l'important és que siguin dades versemblants de persones hospitalitzades.

Una possible manera de definir les dades és la següent:

# Dades de glucosa per a tres pacients en 7 dies.
days_x = np.arange(1, 8)
gl_patients_y = np.array([[110,130,120,145,140,131,120],
                [125,135,151,143,132,120,111],
                [120,125,112,103,108,111,105]])

Fes que vegi una llegenda i/o la descripció del gràfic i els seus eixos, així com diferenciar cada pacient per un color diferent.

Una presentació suggerida del gràfic:

Opció 1. Més senzilla, amb dades realistes.

import numpy as np
import matplotlib.pyplot as plt

# Dades de glucosa per a tres pacients
days_x = np.arange(1, 8)
gl_patients_y = np.array([[110,130,120,145,140,131,120],
                [125,135,151,143,132,120,111],
                [120,125,112,103,108,111,105]])

# Gràfica de línies (eix x--> dies, eix y--> pacients)
plt.plot(days_x, gl_patients_y[0], color='blue', label='Pacient 1', linewidth=2, marker='o')
plt.plot(days_x, gl_patients_y[1], color='green', label='Pacient 2', linewidth=2, linestyle='--', marker='o')
plt.plot(days_x, gl_patients_y[2], color='red', label='Pacient 3', linewidth=2, linestyle='-.', marker='o')

# Afegir títol i etiquetes
plt.title('Evolució Nivell de Glucosa a la Sang (mg/L) de pacients durant 7 Dies')
plt.xlabel('Dies')
plt.ylabel('Nivell de Glucosa (mg/L)')
plt.legend()

# Afegir una graella
plt.grid(True)

# Mostrar la gràfica
plt.tight_layout()
# plt.show()
plt.savefig('exercici11.png')

Opció 2. Conté subgràfics (opcionals en aquest cas) amb dades realistes generades aleatòriament.

import numpy as np
import matplotlib.pyplot as plt

# Configuració dels pacients i dels seus paràmetres
num_pacients = 3
dies = np.arange(1, 8)

# Definició dels rangs per a cada pacient
min_values = [90, 120, 110]  # valor mínim de glucosa
max_values = [130, 160, 150]  # valor màxim de glucosa

colors = ['blue', 'green', 'red']
estils = ['-', '--', '-.']
noms = ['Pacient 1', 'Pacient 2', 'Pacient 3']

# Generació de dades aleatòries
glucose_data = [
    np.random.randint(min_values[i], max_values[i] + 1, 7) for i in range(num_pacients)
]

# Creació de la figura
fig, ax = plt.subplots(figsize=(10, 6))

# Gràfica de línies
for i in range(num_pacients):
    ax.plot(dies, glucose_data[i], color=colors[i], label=noms[i], linewidth=2,
            linestyle=estils[i], marker='o')

ax.set_title('Nivells de Glucosa durant 7 Dies', fontsize=16)
ax.set_xlabel('Dies', fontsize=14)
ax.set_ylabel('Nivell de Glucosa (mg/dL)', fontsize=14)
ax.legend()
ax.grid(True)

# Mostrar el gràfic
plt.tight_layout()
plt.savefig("lineplot1.png")

Diagrames de barres

Com els altres gràfics, es pot fer sense Numpy i aplicant el que hem après amb els diagrames de linies.

Aquests diagrames són molt adequats per mostrar les freqüències d'aparició de cada valor d'una variable.

Per exemple, els gols número de gols de cada jugadora o el número de d'aparicions de cada nucleòtid (A,C,G,T) en una cadena d'ADN.

import matplotlib.pyplot as plt

# Dades del gràfic
titol = "Gràfic de màximes golejadores del Barça (Lliga 2022-2023)."
etiq_y = "Núm gols"
etiq_x = "Jugadores"
llegenda_x = ["A.Oshoala", "S.Paralluelo", "A.Bonmati"]
valors_y = [21, 11, 10]
colors_y = ['tab:purple', 'tab:brown', 'tab:red']

# Creem el gràfic.
bar_container = plt.bar(llegenda_x, valors_y, color=colors_y)
plt.bar_label(bar_container, fmt='%.0f')
plt.ylabel(etiq_y)
plt.xlabel(etiq_x)
plt.title(titol)
#plt.legend(title='Jugadores') 
# Només si vols usar Collab (ipynb)
#plt.show() 
plt.savefig('players.png')

Diagrames de barres agrupats

Amb Matplotlib i Numpy, també podem fer diagrames de barres agrupats; de manera semblant a com hem fet abans gràfics de diverses linies.

Per exemple, suposem que volem veure l'increment dels preus dels lloguers a ciutats de Catalunya.

Les posarem en un array de Numpy, on a l'eix Y hi haurà cada ciutat (Badalona a l'index 0, Barcelona a l'1...) i al de les X els anys (del 2021 al 2023, per exemple)

Les dades del gràfic les hem tret del dataset del preu mitjà dels lloguers als municipis de Catalunya (les hem preprocessat prèviament).

import numpy as np
import matplotlib.pyplot as plt

ciutats = ['Badalona', 'Barcelona', 'Cornella', 'L\'Hospitalet']
anys = ['2021', '2022', '2023']

preusLloguers23 = np.array([
     [730.4,808.02,848.91],  #Badalona
     [918.84,1026.86,1136.4], #Barcelona
     [695.5,723.23,790.39], #Cornella
     [688.74,731.31,794.87] #L'Hospitalet
])
print(preusLloguers23)

# Generem el diagrama de barres per cada any
fig, ax = plt.subplots(figsize=(10, 6))

# Ample de les barres
bar_width = 0.2
# Posicions de les barres per cada any
x = np.arange(len(ciutats))

# Creem les barres per cada any
for i, any in enumerate(anys):
    ax.bar(x + i * bar_width, data[:, i], width=bar_width, label=any)

# Configuració del gràfic
ax.set_xlabel('Ciutats')
ax.set_ylabel('Preu mitjà lloguer')
ax.set_title('Increment preu mitjà lloguer ciutats AMB')
ax.set_xticks(x + bar_width * (len(anys) - 1) / 2)
ax.set_xticklabels(ciutats)
ax.legend()

# Ajustar la visualització
plt.tight_layout()
plt.show()

Classificació de mostres

Tingueu en compte que no sempre us vindran les dades "curades" i en ocasions tindreu, per exemple 100 observacions i les haureu de classificar d'alguna manera: per gènere, per edat...

Suposem que tenim en un array (o llista) els grups sanguinis de 40 pacients (A, O, AB, B) i volem fer un gràfic del número de pacients de cada grup.

Amb la funció de np.unique aconseguirem aquesta agrupació.

Veiem-ho:

import numpy as np
import matplotlib.pyplot as plt

# Definir els grups sanguinis possibles
grups_sanguinis = ['A', 'B', 'AB', 'O']

# Generar aleatòriament els grups sanguinis per a 40 pacients
np.random.seed(0)  # Fixem la llavor per a reproduir els resultats
mostres = np.random.choice(grups_sanguinis, 40)
print(mostres)

# Agrupar les mostres per tipus.
unique, counts = np.unique(mostres, return_counts=True)
data = dict(zip(unique, counts))

print(data)

# Diagrama de barres
plt.figure(figsize=(10, 5))
plt.bar(data.keys(), data.values(), color=['red', 'blue', 'green', 'purple'])
plt.title('Distribució de Grups Sanguinis (Diagrama de Barres)')
plt.xlabel('Grup Sanguini')
plt.ylabel('Nombre de Pacients')
plt.show()

# Diagrama de sectors
plt.figure(figsize=(8, 8))
plt.pie(data.values(), labels=data.keys(), autopct='%1.1f%%', colors=['red', 'blue', 'green', 'purple'])
plt.title('Distribució de Grups Sanguinis (Diagrama de Sectors)')
plt.show()

Les parts més interessants i novedoses del codi es troben al principi:

  1. Es generen les dades aleatòriament.
  2. Encara més important, aquestes linies que guarden els valors únics (unique) i el número d'ocurrències (counts)
  3. Al final guarden tot en un diccionari, per conveniència (però potser no caldria)
# Agrupar les mostres per tipus.
unique, counts = np.unique(mostres, return_counts=True)
data = dict(zip(unique, counts))

Ja sabem el més bàsic de generació de gràfics: la selecció de dades.

2. Crea un diagrama de barres que mostri la freqüència d'aparició dels 4 nucleòtids (A, C, G i T) d'una cadena d'ADN.

dna_seq = "ACGTACGATGCAAGCTAGCTAGCAAGCTAGCTAGCTAGCTAGCTAGCTAGCTAGCTAGCTAGCTAGGTC"

import numpy as np
import matplotlib.pyplot as plt

dna_seq = "ACGTACGATGCAAGCTAGCTAGCAAGCTAGCTAGCTAGCTAGCTAGCTAGCTAGCTAGCTAGCTAGGTC"

freq_seq = {nuc: 0 for nuc in 'ACGT'}

# Comptem les freqüències de les lletres A, C, G i T en una sola passada
for nucleotide in dna_seq:
    if nucleotide in freq_seq:
        freq_seq[nucleotide] += 1

# Diagrama de barres
plt.figure(figsize=(10, 5))
plt.bar(freq_seq.keys(), freq_seq.values(), color=['orange', 'green', 'blue', 'red'])
plt.title('Freqüència d\'Aparició de Lletres en la Cadena d\'ADN')
plt.xlabel('Lletres ADN')
plt.ylabel('Frequència')
plt.ylim(0, max(freq_seq.values()) + 1)
plt.tight_layout()
plt.savefig("lineplot1.png")

Heatmaps

Els heatmaps o mapes de calor són una alternativa molt útil als diagrames de barra compostos, per tal de veure totes les freqüències d'aparició de cada variable.

Generen una taula bidimensional amb els valors de 2 variables, on la llegenda de colors de les dades més altes les marca d'un color intens (pex vermell) i les més baixs d'un altre color oposat (pex blau).

Veiem-ho amb un exemple que ja vam veure a la secció de Numpy: el fitxer CSV de les temperatures de Barcelona dels últims 8 anys

Anem a suposar que ja hem carregat les temperatures dels últims 8 anys i generem el gràfic:

import numpy as np
import matplotlib.pyplot as plt

dades = [
    [2016, 10.7, 11.3, 11.1, 13.6, 16.4, 21.6, 24.9, 24.5, 22.3, 17.1, 12.7, 11.5],
    [2017, 7.9, 11.4, 13.3, 14.2, 18.3, 23.6, 24.2, 24.5, 19.5, 18.6, 12.5, 8.5],
    [2018, 10.5, 6.7, 10.8, 14.7, 17.1, 21.5, 25.3, 25.8, 22.5, 17.0, 12.4, 11.1],
    [2019, 8.1, 11.9, 13.5, 13.4, 15.6, 21.9, 25.4, 25.1, 21.8, 18.5, 11.9, 11.2],
    [2020, 10.0, 12.8, 11.9, 14.3, 19.4, 20.1, 25.0, 25.5, 21.7, 16.4, 14.7, 9.3],
    [2021, 7.7, 11.6, 12.1, 12.9, 17.3, 23.3, 24.8, 24.5, 23.0, 18.1, 11.3, 10.9],
    [2022, 10.2, 11.8, 10.8, 14.1, 20.7, 24.7, 26.7, 27.2, 22.5, 20.7, 15.2, 12.6],
    [2023, 9.2, 10.3, 14.1, 16.1, 18.1, 23.4, 25.5, 26.0, 23.2, 20.2, 14.8, 12.1]
]
data = np.array(dades)

# Separar les columnes per a les etiquetes dels anys i les temperatures
anys = data[:, 0]
temperatures = data[:, 1:]

# Crear el heatmap anotat
fig, ax = plt.subplots(figsize=(12, 8))
cax = ax.matshow(temperatures, cmap='coolwarm')

# Afegir la barra de color
cbar = fig.colorbar(cax, ax=ax, orientation='vertical')
cbar.set_label('Temperatura (°C)')

# Afegir anotacions
for (i, j), val in np.ndenumerate(temperatures):
    ax.text(j, i, f'{val:.1f}', ha='center', va='center', color='black')

# Configurar les etiquetes
ax.set_xlabel('Mesos')
ax.set_ylabel('Anys')
ax.set_title('Heatmap de les temperatures mensuals a Barcelona (2016-2023)')

# Ajustar les etiquetes de l'eix X (mesos)
ax.set_xticks(np.arange(12))
ax.set_xticklabels(['Gen', 'Feb', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Oct', 'Nov', 'Des'])

# Ajustar les etiquetes de l'eix Y (anys)
ax.set_yticks(np.arange(data.shape[0]))
ax.set_yticklabels([str(int(year)) for year in anys])

plt.savefig("matplotlib7.png")

Però a la vida real no tindrem les dades tan curades, haurem de carregar-les d'un o més fonts i preprocessar-les.

En el nostre cas, necessitem descarregar-nos les dades si no les tenim al disc (connectar-se a una altra URL té un cost computacional i ambiental considerable).

Recordeu com podem aconseguir aquesta descàrrega i lectura de fitxers CSV que són en una URL externa:

xtec.dev -> Lectura i tractament de fitxers amb Python

Un cop les tenim, carreguem les dades de les temperatures en un array, i filtrem únicament les 8 últimes.

Guardem els últims anys (2016 - 2023) en un vector 1D separat de les temperatures (sinó, tindrem errors ja que els arrays han de ser homogenis).

La resta de passos, per a generar el gràfic, són els mateixos.

El codi final queda així:

import numpy as np
# Per a ipynb: 
# !pip install nptyping
from nptyping import NDArray, Int, Float
import os.path
from urllib.request import urlretrieve
import matplotlib.pyplot as plt

url : str = 'https://opendata-ajuntament.barcelona.cat/data/dataset/73f09843-ab4e-4f13-81fb-b801ca371909/resource/0e3b6840-7dff-4731-a556-44fac28a7873/download/temperaturesbcndesde1780_2023.csv'

file : str = 'temperaturesbcn_2023.csv'

# Descarregar el fitxer CSV només si no existeix
if not os.path.isfile(file):
    try:
        urlretrieve(url, file)
        print(f"Fitxer {file} descarregat correctament.")
    except Exception as e:
        print(f"No s'ha pogut descarregar el fitxer des de {url}: {str(e)}")

# Lectura del tot el fitxer CSV excepte la capçalera
data = np.loadtxt('temperaturesbcn_2023.csv', delimiter=',', skiprows=1)

# Seleccionem les últimes 8 files de temperatures (2016 - 2023)
# Separem la columna dels anys i la eliminem de l'array de temperatures.
temps_array = data[-8:,1:13]
anys_array = data[-8:,0]

# print(temps_array)
# print(anys_array)

# Crear el heatmap anotat
fig, ax = plt.subplots(figsize=(12, 8))
cax = ax.matshow(temps_array, cmap='coolwarm')

# Afegir la barra de color
cbar = fig.colorbar(cax, ax=ax, orientation='vertical')
cbar.set_label('Temperatura (°C)')

# Afegir anotacions i etiquetes
for (i, j), val in np.ndenumerate(temps_array):
    ax.text(j, i, f'{val:.1f}', ha='center', va='center', color='black')

ax.set_xlabel('Mesos')
ax.set_ylabel('Anys')
ax.set_title('Heatmap de les temperatures mensuals a Barcelona (2016-2023)')

# Ajustar les etiquetes de l'eix X (mesos)
ax.set_xticks(np.arange(12))
ax.set_xticklabels(['Gen', 'Feb', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Oct', 'Nov', 'Des'])

# Ajustar les etiquetes de l'eix Y (anys)
ax.set_yticks(np.arange(len(anys_array)))  # Canviar el número de ticks a 8 (les últimes 8 files)
ax.set_yticklabels([str(int(year)) for year in anys_array])

plt.savefig("matplotlib7.png")

El resultat del gràfic és el mateix, però el resolem amb un codi útil en més contextos i mantenible.

3.

Crea un Heatmap a partir de l'exemple dels preus de lloguer, en vermell els preus més alts i en verd els més baixos.

El codi de partida és el següent:

import numpy as np
import matplotlib.pyplot as plt

ciutats = ['Badalona', 'Barcelona', 'Cornella', 'L\'Hospitalet']
anys = ['2021', '2022', '2023']

preusLloguers23 = np.array([
     [730.4,808.02,848.91],  #Badalona
     [918.84,1026.86,1136.4], #Barcelona
     [695.5,723.23,790.39], #Cornella
     [688.74,731.31,794.87] #L'Hospitalet
])
print(preusLloguers23)

import numpy as np
import matplotlib.pyplot as plt

ciutats_llista : list[str] = ['Badalona', 'Barcelona', 'Cornella', 'L\'Hospitalet']
anys_llista : list[str] = ['2021', '2022', '2023']

preusLloguers23 = np.array([
     [730.4,808.02,848.91],  #Badalona
     [918.84,1026.86,1136.4], #Barcelona
     [695.5,723.23,790.39], #Cornella
     [688.74,731.31,794.87] #L'Hospitalet
])
print(preusLloguers23)
# Em ve de gust trasposar la matriu per presentar millor el heatmap.
preusLloguers23 = preusLloguers23.T

# Crear el heatmap anotat
fig, ax = plt.subplots(figsize=(10, 6))
# Catàleg colors a: 
# https://matplotlib.org/stable/users/explain/colors/colormaps.html
cax = ax.matshow(preusLloguers23, cmap='YlOrRd')

# Afegir la barra de color
cbar = fig.colorbar(cax, ax=ax, orientation='vertical')
cbar.set_label('Preus (en €)')

# Afegir anotacions
for (i, j), val in np.ndenumerate(preusLloguers23):
    ax.text(j, i, f'{val:.1f}', ha='center', va='center', color='black')

# Configurar les etiquetes
ax.set_xlabel('Ciutats')
ax.set_ylabel('Anys')
ax.set_title('Heatmap dels preus de lloguers aprop de Barcelona (2021-2023)')

# Ajustar les etiquetes de l'eix X (ciutats)
ax.set_xticks(np.arange(len(ciutats_llista)))
ax.set_xticklabels(ciutats_llista)

# Ajustar les etiquetes de l'eix Y (anys)
ax.set_yticks(np.arange(len(anys_llista)))
ax.set_yticklabels(anys_llista)

plt.savefig("matplotlib7.png")

Scatter Plot

Els scatter plots són gràfics de punts en un pla (2D) o espai (3D). Podem pintar aqusts punts de diversos colors o formes si usem subplots. Això als científics els va molt bé per a classificar a mostres d'individus o espècies per dues carecterístiques que puguin tenir relació (per exemple, podem fer un mapa de punts de 2 colors: home i dona; tenint en compte la seva alçada i pes).

Però millor ho veiem amb un dataset d'exemple mostra les diverses espècies de la planta Iris. Aquest conjunt de dades, de 50 plantes de 3 espècies i 4 carecterístiques importants, és un recurs típic per introduïr-nos al data science.

import numpy as np
import matplotlib.pyplot as plt
import requests
import csv
import os

url_file : str = 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv'
local_file : str = 'iris.csv'

# Verificar si el fitxer ja existeix localment
if not os.path.exists(local_file):
    # Descarregar el fitxer si no existeix
    response = requests.get(url_file)
    with open(local_file, 'wb') as f:
        f.write(response.content)

# Llegir les dades del fitxer CSV amb NumPy
with open(local_file, 'r') as f:
    reader = csv.reader(f)
    headers = next(reader)  # Saltar la fila de capçaleres
    # Ho llegirà com array d'strings per evitar errors.
    data = np.array([row for row in reader])

# Convertir les dades a arrays 1D de Numpy amb tipus ben definit.
sepal_length = data[:, 0].astype(float)
petal_length = data[:, 2].astype(float)
species = data[:, 4]

# Crear un subgràfic de punts per a cada espècie (n'hi ha 3)
plt.figure(figsize=(10, 6))

# Recorrem les espècies úniques i dibuixem els punts corresponents
for specie in np.unique(species):
    # Seleccionem les longituds de pètals i sèpals per a l'espècie actual
    petal_lengths = petal_length[species == specie]
    sepal_lengths = sepal_length[species == specie]
    # Dibuixem els punts per a aquesta espècie amb l'etiqueta corresponent
    plt.scatter(petal_lengths, sepal_lengths, label=specie)

plt.xlabel('Longitud dels pètals')
plt.ylabel('Longitud de les sèpals')
plt.title('Distribució de longituds de pètals i sèpals per espècie (Iris Dataset)')
plt.legend()
plt.grid(True)
plt.savefig("matplotlib8.png")

Amb aquest gràfic, podem deduïr que la Iris Setosa sol tenir els pètals més petits respecte les altres 2 espècies i la Virginica els més grans.


Histogrames i corbes de distribució normal.

Una de les tasques més comuns en estadística (tant descriptiva = dades, com inferencial = probabilitats) és generar gràfics per veure quina distribució presenten les dades un cop sabem la freqüència de cada ocurrència d'una variable; i gràcies a Numpy i Matplotlib podem generar aquest gràfic molt fàcilment.

Una de les distribucions teòriques més utilitzada a la pràctica és la distribució normal, també anomenada distribució gaussiana en honor al matemàtic Carl Friedrich Gauss.

Ara veurem la potència que té Numpy per a generar una colecció de dades que segueixen la distribució Normal i Matplotlib per a dibuixar tant un histograma per veure la distribució i una línia per veure com s'assembla la distribució que tenim respecte la Normal.

Provem aquest exemple fictici del nivell de colesterol a la sang de 300 pacients.

import numpy as np
import matplotlib.pyplot as plt

# Paràmetres per a la distribució normal
mean = 180  # mitjana del colesterol (mg/dL)
std_dev = 25  # desviació estàndard (mg/dL)
n_samples = 300  # nombre de mostres

# Generar dades aleatòries amb una distribució normal
cholesterol_levels = np.random.normal(mean, std_dev, n_samples)

# Crear l'histograma
plt.hist(cholesterol_levels, bins=20, density=True, alpha=0.6, color='g', edgecolor='black')

# Crear la corba de distribució normal
xmin, xmax = plt.xlim()
x = np.linspace(xmin, xmax, 100)
p = np.exp(-0.5*((x-mean)/std_dev)**2) / (std_dev * np.sqrt(2 * np.pi))
plt.plot(x, p, 'k', linewidth=2)

# Afegir títol i etiquetes
plt.title('Distribució dels Nivells de Colesterol')
plt.xlabel('Nivells de Colesterol (mg/dL)')
plt.ylabel('Freqüència')

# Afegir línia vertical a la mitjana
plt.axvline(mean, color='b', linestyle='dashed', linewidth=2, label='Mitjana')

# Afegir línies verticals als intervals de confiança (±1 desviació estàndard)
plt.axvline(mean - std_dev, color='r', linestyle='dotted', linewidth=2, label='-1 Desviació estàndard')
plt.axvline(mean + std_dev, color='r', linestyle='dotted', linewidth=2, label='+1 Desviació estàndard')

plt.legend()
plt.savefig("matplotlib9.png")

Exercicis. Gràfics anàlisi de la qualitat de vins.

El dataset de qualitat del vi (Wine Quality Dataset) és un conjunt de dades molt utilitzat en l'anàlisi de dades i el machine learning.

Conté informació sobre les característiques físiques i químiques dels vins, així com les puntuacions de qualitat que han rebut en tests de tast.

Pots trobar el resum del dataset a la seva web oficial:

De totes les columnes, les que ens interessa guardar per a les gràfiques que farem són:

  • 9 - pH (els vins són àcids, el seu PH pot oscilar entre 2.5 i 4.5)
  • 11 - alcohol (en %, sol oscilar entre 9 i 15)
  • 12 - quality (puntuació entre 0 i 10)

Ens han demanat els següents gràfics per tal d'analitzar la qualitat dels vins que tenim:

4. Crear un gràfic de dispersió (scatter plot) que mostri la relació entre dues característiques del vi, per exemple, el contingut d'alcohol i la puntuació de qualitat. Pots provar qualsevol altre mesura, com el PH.

5. Crear un histograma que mostri la distribució de la puntuació de qualitat dels vins i afegeix la corba de distribució normal.

Primer, creem un mòdul d'utilitat wineutils.py per llegir (i descarregar només si cal) els fitxers:

import numpy as np
import urllib3 
import os

def get_file(url, file):
    http = urllib3.PoolManager()
    if not os.path.isfile(file):
        response = http.request("GET", url)
        
        # Comprovar si la petició ha estat exitosa
        if response.status == 200:
            print(f"Descarregant el fitxer: {file}")
            with open(file, "wb") as f:
                f.write(response.data)
        else:
            print(f"Error en descarregar el fitxer: {response.status}")

    return file

def read_csv(local_file):
    data = np.genfromtxt(local_file, delimiter=';', skip_header=1)
    return data

Ex1.

import numpy as np
import matplotlib.pyplot as plt
import wineutils

if __name__ == '__main__':
        
    url_file = 'https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv'
    local_file = 'winequality-red.csv'

    file = wineutils.get_file(url_file, local_file)
    data = wineutils.read_csv(file)

    # Seleccionar les columnes d'alcohol (column index 10) i qualitat (column index 11)
    alcohol_content = data[:, 9]  # Contingut d'alcohol
    quality_scores = data[:, 11]   # Puntuació de qualitat

    # Crear el gràfic de dispersió
    plt.figure(figsize=(10, 6))
    plt.scatter(alcohol_content, quality_scores, alpha=0.6, edgecolors='w')
    plt.title('Relació entre el Contingut d\'Alcohol i la Puntuació de Qualitat')
    plt.xlabel('% Alcohol del vi')
    plt.ylabel('Puntuació de Qualitat')
    plt.grid(True)
    # plt.show() No funciona sempre.
    plt.savefig('wine_scatter2.png')

Ex5.

import numpy as np
import matplotlib.pyplot as plt
import wineutils

if __name__ == '__main__':
        
    url_file = 'https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv'
    local_file = 'winequality-red.csv'

    file = wineutils.get_file(url_file, local_file)
    data = wineutils.read_csv(file)

    # Seleccionar la columna de qualitat (column index 11)
    quality_scores = data[:, 11]   # Puntuació de qualitat

    # Paràmetres per a la distribució normal
    mean = np.mean(quality_scores)
    std_dev = np.std(quality_scores)

    # Crear l'histograma
    plt.hist(quality_scores, bins=6, density=True, alpha=0.6, color='lightcoral', edgecolor='black')

    # Crear la corba de distribució normal
    xmin, xmax = plt.xlim()
    x = np.linspace(xmin, xmax, 100)
    p = np.exp(-0.5*((x-mean)/std_dev)**2) / (std_dev * np.sqrt(2 * np.pi))
    plt.plot(x, p, 'k', linewidth=2)

    plt.title('Distribució de la Qualitat del Vi')
    plt.xlabel('Qualitat')
    plt.ylabel('Freqüència')

    # Afegir línia vertical a la mitjana
    plt.axvline(mean, color='r', linestyle='dashed', linewidth=2, label='Mitjana')
    plt.legend()

    plt.savefig('wine_dist.png')

Gràfics a la web amb FastAPI

A continuació veurás com servir els gràfics que has generat via web.

Arrenca una màquina Windows Subsytem for Linux (WSL).

Clona el projecte https://gitlab.com/xtec/python/matplotlib

> git clone https://gitlab.com/xtec/python/matplotlib

Obre el projecte amb VS Code:

> code matplotlib

Ara, prova de mostrar algún dels teus gràfics a la web. 📈📊


Referències.