Salta el contingut

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.