Biblioteques Python de visualització
Matplotlib proporciona el control total sobre la figura, Seaborn simplifica la creació de gràfics estadístics elegants i Plotly permet crear visualitzacions interactives i dashboards web. Cadascuna té el seu cas d'ús ideal.
Quina biblioteca triar
| Biblioteca | Punt fort | Cas d'ús ideal |
|---|---|---|
| Matplotlib | Control total sobre cada element de la figura | Figures científiques, publicacions, control fi del format |
| Seaborn | Gràfics estadístics amb molt poc codi, estètica per defecte | Exploració ràpida de dades (EDA), gràfics estadístics |
| Plotly | Interactivitat (zoom, hover, filtres), exportació a HTML | Dashboards web, notebooks interactius, presentacions |
No són excloents
Seaborn es construeix sobre Matplotlib: un objecte Axes retornat per una funció de Seaborn es pot seguir personalitzant amb mètodes de Matplotlib (ax.set_title(), ax.set_xlim()...). És habitual combinar Seaborn per a l'estructura ràpida del gràfic i Matplotlib per als retocs finals.
Matplotlib: anatomia d'una figura
Tota figura de Matplotlib es construeix sobre una jerarquia d'objectes: la Figure (el llenç sencer) conté un o més Axes (cadascun és un sistema de coordenades, no confondre amb "eix" en singular), i cada Axes conté els elements visuals (línies, barres, etiquetes, llegenda).
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(8, 5)) # fig = llenç; ax = el "gràfic" en si
ax.plot([1, 2, 3, 4], [10, 15, 13, 18], marker='o')
ax.set_title("Vendes setmanals")
ax.set_xlabel("Setmana")
ax.set_ylabel("Import (€)")
plt.tight_layout()
plt.savefig("vendes_setmanals.png", dpi=150, bbox_inches="tight")
plt.show()
Subplots: múltiples gràfics en una figura
plt.subplots(files, columnes) retorna una graella d'objectes Axes que es poden indexar i personalitzar de manera independent.
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import numpy as np
plt.style.use('seaborn-v0_8-whitegrid')
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
fig.suptitle('Anàlisi de Vendes 2026', fontsize=16, fontweight='bold', y=1.02)
# Gràfic 1: evolució mensual (línia)
mesos = ['Gen', 'Feb', 'Mar', 'Abr', 'Mai', 'Jun']
vendes_2026 = [12500, 11800, 15200, 14100, 16800, 18300]
axes[0, 0].plot(mesos, vendes_2026, 'b-o', linewidth=2)
axes[0, 0].set_title('Evolució mensual')
axes[0, 0].yaxis.set_major_formatter(mticker.FuncFormatter(lambda x, p: f'{x:,.0f}€'))
# Gràfic 2: comparació per categoria (barres horitzontals)
categories = ['Electrònica', 'Roba', 'Llar', 'Esports']
totals = [85200, 62300, 48900, 37600]
axes[0, 1].barh(categories, totals, color='#2196F3', edgecolor='white')
axes[0, 1].set_title('Vendes per categoria')
# Gràfic 3: distribució (histograma)
np.random.seed(42)
valors = np.random.lognormal(4.0, 0.8, 800)
axes[1, 0].hist(valors, bins=40, color='steelblue', edgecolor='white', alpha=0.8)
axes[1, 0].axvline(np.median(valors), color='red', linestyle='--', label='Mediana')
axes[1, 0].set_title('Distribució del valor de comanda')
axes[1, 0].legend()
# Gràfic 4: relació entre variables (scatter)
preus = np.random.uniform(10, 500, 200)
quantitats = np.clip(1000 / preus + np.random.normal(0, 0.5, 200), 0.5, 10)
sc = axes[1, 1].scatter(preus, quantitats, c=preus * quantitats, cmap='viridis', alpha=0.6)
axes[1, 1].set_title('Preu vs. quantitat venuda')
plt.colorbar(sc, ax=axes[1, 1], label='Valor total (€)')
plt.tight_layout()
plt.savefig('dashboard_vendes.png', dpi=150, bbox_inches='tight')
El paràmetre figsize i el dpi
figsize es defineix en polzades (no píxels) i dpi (dots per inch) determina la resolució final en exportar. Per a figures destinades a una presentació en pantalla, dpi=150 és suficient; per a impressió, és recomanable dpi=300.
Seaborn: gràfics estadístics
Seaborn s'integra directament amb pandas i ofereix funcions d'alt nivell per a gràfics estadístics habituals, amb una estètica professional per defecte.
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
sns.set_theme(style="whitegrid", palette="viridis")
np.random.seed(42)
df = pd.DataFrame({
'categoria': np.random.choice(['Electrònica', 'Roba', 'Llar', 'Esports'], 500),
'import': np.random.lognormal(4.5, 0.7, 500),
'mes': np.random.choice(range(1, 13), 500),
})
# Box plot: distribució per categoria
fig, ax = plt.subplots(figsize=(9, 5))
sns.boxplot(data=df, x='categoria', y='import', ax=ax)
ax.set_title('Distribució de l\'import de venda per categoria')
# Violin plot: distribució amb densitat
fig, ax = plt.subplots(figsize=(9, 5))
sns.violinplot(data=df, x='categoria', y='import', ax=ax)
ax.set_title('Densitat de l\'import de venda per categoria')
Heatmaps
Els heatmaps de Seaborn son ideals per visualitzar matrius de correlació o taules de doble entrada (per exemple, vendes per mes i categoria).
# Matriu de correlació
df_numeric = pd.DataFrame(np.random.randn(200, 5),
columns=['vendes', 'marge', 'unitats', 'descompte', 'visites'])
corr = df_numeric.corr()
fig, ax = plt.subplots(figsize=(7, 6))
sns.heatmap(corr, annot=True, fmt='.2f', cmap='coolwarm', center=0,
vmin=-1, vmax=1, ax=ax)
ax.set_title('Matriu de correlació')
# Taula pivotada: vendes per mes i categoria
pivot = df.pivot_table(values='import', index='mes', columns='categoria', aggfunc='sum')
fig, ax = plt.subplots(figsize=(8, 7))
sns.heatmap(pivot, annot=True, fmt='.0f', cmap='YlGnBu', ax=ax)
ax.set_title('Vendes totals per mes i categoria')
annot=True per a llegibilitat
En heatmaps amb poques cel·les (menys de ~15x15), annot=True mostra el valor numèric dins de cada cel·la, eliminant la necessitat de consultar la barra de color per llegir valors exactes — una aplicació directa del principi del data-ink ratio de Tufte.
Plotly: gràfics interactius
Plotly genera gràfics HTML interactius (zoom, hover amb detalls, filtres de llegenda) que es poden incrustar en notebooks, pàgines web o aplicacions Dash.
import plotly.express as px
import pandas as pd
import numpy as np
np.random.seed(42)
dates = pd.date_range('2026-01-01', '2026-12-31', freq='D')
df = pd.DataFrame({
'data': dates,
'vendes': np.random.lognormal(8, 0.5, len(dates)) +
np.sin(np.arange(len(dates)) * 2 * np.pi / 365) * 5000,
'categoria': np.random.choice(['Electrònica', 'Roba', 'Llar'], len(dates)),
})
fig = px.line(
df.groupby('data')['vendes'].sum().reset_index(),
x='data', y='vendes',
title='Evolució de vendes diàries 2026',
labels={'data': 'Data', 'vendes': 'Import (€)'},
template='plotly_white'
)
fig.update_traces(line=dict(width=1.5, color='royalblue'))
fig.add_vline(x='2026-11-27', line_dash='dash', line_color='red',
annotation_text='Black Friday')
fig.update_layout(hovermode='x unified')
fig.write_html('vendes_temporal.html')
Subplots i dashboards amb Plotly
from plotly.subplots import make_subplots
import plotly.graph_objects as go
fig_multi = make_subplots(
rows=1, cols=2,
subplot_titles=['Vendes per categoria', 'Distribució percentual'],
specs=[[{"type": "bar"}, {"type": "pie"}]]
)
vendes_cat = df.groupby('categoria')['vendes'].sum().reset_index()
fig_multi.add_trace(
go.Bar(x=vendes_cat['categoria'], y=vendes_cat['vendes']),
row=1, col=1
)
fig_multi.add_trace(
go.Pie(labels=vendes_cat['categoria'], values=vendes_cat['vendes'], hole=0.3),
row=1, col=2
)
fig_multi.update_layout(height=450, showlegend=False, title_text='Resum de vendes')
fig_multi.write_html('resum_vendes.html')
Dash: aplicacions web a partir de Plotly
Dash, també de Plotly, permet construir aplicacions web completes de visualització de dades amb Python pur, sense escriure JavaScript. Una app Dash mínima:
from dash import Dash, dcc, html, Input, Output
import plotly.express as px
app = Dash(__name__)
app.layout = html.Div([
html.H1("Dashboard de vendes"),
dcc.Dropdown(
id='filtre-categoria',
options=[{'label': c, 'value': c} for c in df['categoria'].unique()],
value='Electrònica'
),
dcc.Graph(id='grafic-vendes')
])
@app.callback(
Output('grafic-vendes', 'figure'),
Input('filtre-categoria', 'value')
)
def actualitza_grafic(categoria):
df_filtrat = df[df['categoria'] == categoria]
return px.line(df_filtrat.groupby('data')['vendes'].sum().reset_index(),
x='data', y='vendes', title=f'Vendes — {categoria}')
if __name__ == '__main__':
app.run(debug=True, port=8050)
El patró és sempre el mateix: un layout declaratiu (els components visuals) i un o més callbacks que reaccionen als esdeveniments d'entrada (un dropdown, un slider) i retornen una figura actualitzada, de manera anàloga als slicers de Power BI o als filtres de Metabase.
Activitat AC5074/07/02 — Galeria de visualitzacions
Sobre un dataset de mobilitat urbana (es proporcionarà a l'aula), crea vuit gràfics diferents que cobreixin almenys quatre dels cinc propòsits vistos a Principis de visualització: comparació, evolució temporal, distribució, composició i relació. Usa com a mínim dues de les tres biblioteques (Matplotlib, Seaborn, Plotly).
Quan Python i quan una eina de BI
Python (Matplotlib/Seaborn/Plotly) és superior quan la visualització forma part d'un procés reproduïble (un informe automatitzat, un notebook d'anàlisi) o quan es necessita un tipus de gràfic molt específic no disponible a les eines de BI. Power BI o Metabase son superiors quan l'usuari final ha de poder explorar les dades de manera autònoma, sense escriure codi.