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.

Instal·lar i provar Matplotlib

Si ja has instal·lat en el teu projecte Numpy o Pandas, ja podràs importar Matplotlib.

Pots reaprofitar el projecte que usaves a Numpy o bé crear-ne un de nou. Recorda com es feia:

$ mkdir numpyplots
$ cd numpyplots
$ python3 -m venv .venv
$ source .venv/bin/activate
$ pip install numpy

Si no s'instal·la matplotlib aplica aquesta instrucció:

$ pip install matplotlib

També recomanem que guardis la versió de les llibreries que has usat:

$ pip freeze > requirements.txt

Consideracions d'ús de Jupyter Notebook.

Com ja saps, molts/es científics/ques se senten més còmodes treballant amb Jupyter Notebooks, fitxers amb format .IPYNB que es poden executar a plataformes com Google Collab o Kaggle i així poden lluïr de Data Science sense instal·lar i mantenir programari.

Nosaltres som informàtics i no usarem aquests quaderns com a producte final, com a molt ens interessarà consultar i provar fitxers .IPYNB per explorar els datasets; la nostra prioritat és que les dades estiguin integrades en qualsevol aplicació web o d'altres tipus.

Simplement, que sabeu que si trobeu linies com aquestes:

%matplotlib inline

Només són necessàries si useu Jupyter Notebook.

import matplotlib.pyplot as plt
# If you're using Jupyter Notebook, you may also want to run the following
# line of code to display your code in the notebook:
%matplotlib inline

plt.plot(a)
# If you are running from a command line, you may need to do this:
# >>> plt.show()

També heu de saber que si useu codi de Jupyter Notebook podeu mostrar els gràfics amb aquesta instrucció

plt.show()

En canvi, si esteu en un projecte de Python (.py) el més habitual és guardar el gràfic resultant en un fitxer d'imatge .png per veure els resultats.

plt.savefig('students.png')

Més endavant crearem alguna aplicació web per a mostrar aquests gràfics (que són fitxers d'imatge).

Crear gràfics 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')
X = np.arange(-5, 5, 0.15)
Y = np.arange(-5, 5, 0.15)
X, Y = np.meshgrid(X, Y)
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 com el color.

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))

Exercici. 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. Que siguin dades versemblants de persones hospitalitzades. Intenta que es 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) 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()

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

``py import numpy as np import matplotlib.pyplot as plt

Generació de dades sintètiques per als 3 pacients durant 7 dies

days = np.arange(1, 8) glucose_patient1 = np.random.normal(110, 10, 7) glucose_patient2 = np.random.normal(140, 15, 7) glucose_patient3 = np.random.normal(130, 12, 7)

Creació de la figura

fig, ax = plt.subplots(figsize=(10, 6))

Gràfica de línies

ax.plot(days, glucose_patient1, color='blue', label='Pacient 1', linewidth=2, marker='o') ax.plot(days, glucose_patient2, color='green', label='Pacient 2', linewidth=2, linestyle='--', marker='o') ax.plot(days, glucose_patient3, color='red', label='Pacient 3', linewidth=2, linestyle='-.', marker='o')

Afegir títol i etiquetes

ax.set_title('Evolució Nivell de Glucosa a la Sang (mg/L) durant 7 Dies') ax.set_xlabel('Dia') ax.set_ylabel('Nivell de Glucosa (mg/L)')

Afegir una graella

ax.grid(True) plt.tight_layout() plt.show() ``

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.

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')

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

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)

Ja veureu com us surt.

Compartim amb vosaltres l'adreça del dataset original del preu mitjà dels lloguers als municipis de Catalunya

import numpy as np
import matplotlib.pyplot as plt

especialitats = ['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(especialitats))

# 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(especialitats)
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.


Pendent revisar a partir d'aquí ...

Histogrames i corbes de distribució

import numpy as np
import matplotlib.pyplot as plt

# Paràmetres per a la distribució normal
mean = 200  # mitjana del colesterol (mg/dL)
std_dev = 30  # 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')

# Afegir llegenda
plt.legend()

# Mostrar el gràfic
plt.show()

També veure els de violí (2 histogrames).

Diagrames de dispersió de punts (scatter)

Quadres heatmap.

Més endavant (o en un altre lloc) veurem les possibilitats de Matplotlibs sobre Numpy:

Matplotlip --> Annotated Heatmaps

2D and 3D Functions

Numpy Normal and Binomial distributions