Python, de la zero Lecția 44 / 60

Plotting: matplotlib, seaborn, plotly - cum o alegi pe a ta

Trei biblioteci de plotting, trei filozofii si la care apelezi in functie de audienta.

Vizualizarea în Python e fragmentată. Nu există o singură bibliotecă canonică așa cum pandas e canonic pentru DataFrame-uri sau scikit-learn e canonic pentru ML clasic. Există trei biblioteci pe care le folosesc majoritatea oamenilor, fiecare construită pentru o audiență diferită, iar lucrul productiv nu e să alegi una și să te prefaci că celelalte nu există, ci să știi la care să apelezi în funcție de cine se va uita la grafic.

În această lecție: matplotlib, seaborn și plotly. La ce e bună fiecare, de ce arată valorile lor implicite așa cum arată, și același grafic scris de trei ori ca să vezi compromisurile alăturate.

matplotlib: fundația

matplotlib există din 2003. John Hunter a scris-o ca un port al interfeței de plotting din MATLAB, ca neuroscientiștii să-și păstreze memoria musculară când treceau la Python. Acea istorie e vizibilă în practic fiecare alegere de API a bibliotecii — interfața globală plt.plot, plt.title, plt.show e a MATLAB-ului. Valorile implicite sunt de publicații științifice de la începutul anilor 2000: fundal alb, linie albastră, etichete de axă sans-serif, o estetică ușor masivă care nu a îmbătrânit foarte bine.

Dar matplotlib e fundația. Seaborn e construit pe ea. df.plot() din pandas o apelează. Majoritatea celorlalte biblioteci de plotting Python ori randează prin matplotlib, ori îi urmează convențiile. Cunoașterea matplotlib la nivelul „pot să citesc codul și să-l ajustez” e nenegociabilă, chiar dacă folosești în principal biblioteci de nivel mai înalt.

Tiparul de folosit, de fiecare dată, e interfața orientată pe obiecte:

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 10, 100)
y = np.sin(x)

fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(x, y, label="sin(x)", color="steelblue", linewidth=2)
ax.set_xlabel("x")
ax.set_ylabel("sin(x)")
ax.set_title("A sine wave")
ax.legend()
ax.grid(alpha=0.3)
fig.tight_layout()
fig.savefig("sine.png", dpi=150)
plt.show()

fig e întreaga figură (canvasul). ax sunt axele (regiunea efectivă de plotting din interior). Fiecare personalizare se întâmplă prin ax.ceva. Acesta e API-ul pe care ar trebui să-l folosești mereu. Interfața globală în stil MATLAB — plt.plot(x, y); plt.title("...") — funcționează, dar se bazează pe stare ascunsă, se rupe în feluri subtile în notebook-uri și pipeline-uri și e imposibil de compus curat în figuri cu mai multe panouri. Folosește fig, ax = plt.subplots().

Pentru mai multe panouri, cere un grid de la început:

fig, axes = plt.subplots(2, 2, figsize=(10, 8))
axes[0, 0].plot(x, np.sin(x))
axes[0, 1].plot(x, np.cos(x))
axes[1, 0].plot(x, np.tan(x))
axes[1, 1].plot(x, x ** 2)
fig.tight_layout()

Când să apelezi direct la matplotlib: când ai nevoie de control la nivel de pixel. Adnotări la coordonate specifice. Figuri cu mai multe panouri și axe partajate. Legende personalizate. Orice destinat unui raport PDF sau unei lucrări. Matplotlib 3.x — pe care vei fi în 2026 — are valori implicite decente dacă setezi stilul: plt.style.use("seaborn-v0_8-whitegrid") e o primă linie comună care face lucrurile să arate imediat mai puțin 2003.

seaborn: matplotlib pentru grafică statistică

Seaborn e un strat subțire peste matplotlib scris de Michael Waskom. Face două lucruri: livrează valori implicite sensibile ca graficele să arate modern din cutie și are funcții de nivel mai înalt pentru tipurile de grafice pe care le vrei efectiv când faci analiză de date.

Modelul mental e: seaborn știe despre DataFrame-uri. Îi dai un DataFrame și îi spui ce coloane să mapeze pe x, y, culoare și formă. El face agregarea și layout-ul. Pe dedesubt, apelează matplotlib, ceea ce înseamnă că tot ce ai învățat despre fig, ax se aplică în continuare; poți scoate axele matplotlib din orice apel seaborn și să personalizezi.

Graficele pe care le vei folosi în 90% din cazuri:

import seaborn as sns
import pandas as pd

# Load tips dataset bundled with seaborn for examples
tips = sns.load_dataset("tips")

# Scatter with color encoding
sns.scatterplot(data=tips, x="total_bill", y="tip", hue="time", style="smoker")

# Boxplot grouped by category
sns.boxplot(data=tips, x="day", y="total_bill", hue="sex")

# Heatmap (great for correlation matrices)
corr = tips.select_dtypes("number").corr()
sns.heatmap(corr, annot=True, cmap="coolwarm", center=0)

# Pairplot — every numeric column against every other
sns.pairplot(tips, hue="time")

Cei doi parametri care apar peste tot sunt hue= (codare prin culoare — pasează orice coloană, categorică sau continuă) și style= (forma markerului, doar pentru categorice). Împreună îți permit să codifici trei sau patru variabile deodată pe un grafic 2-D fără să scrii niciun cod de agregare.

Funcțiile seaborn relplot, catplot și displot (funcțiile „figure-level”) sunt versiunea care gestionează facetingul — împărțirea unui grafic într-un grid după altă variabilă:

sns.relplot(
    data=tips,
    x="total_bill", y="tip",
    hue="day", col="time", row="sex",
    kind="scatter",
)

Asta înseamnă șase panouri, codate prin culoare după zi, fără buclă manuală. Când explorezi un set de date și vrei o citire rapidă a „cum se schimbă X între aceste grupuri”, seaborn la nivel de figură e cea mai rapidă unealtă din Python.

Când să apelezi la seaborn: orice muncă analitică unde audiența ești tu sau un coleg. Notebook-uri de EDA. Rapoarte statistice. Orice unde vrei să arate rezonabil fără să negociezi cu matplotlib despre dimensiuni de font.

plotly: grafice interactive care funcționează pe web

matplotlib și seaborn produc imagini statice — PNG, PDF, SVG. Plotly produce grafice interactive. Treci cu mouse-ul peste un punct și un tooltip arată valoarea. Tragi o regiune ca să faci zoom. Comuti trace-uri pe pornit și oprit dând click pe legendă. Output-ul e HTML+JavaScript și se randează inline în Jupyter, în vizualizarea de notebook din VS Code, într-o aplicație Streamlit sau Dash sau în orice pagină web.

Biblioteca Python plotly are două straturi. Cel de nivel înalt, plotly.express, e ce vei folosi pentru 80% din grafice:

import plotly.express as px

fig = px.scatter(
    tips,
    x="total_bill", y="tip",
    color="time", symbol="smoker",
    size="size", hover_data=["day"],
    title="Tips vs total bill",
)
fig.show()

Acesta e echivalentul plotly al scatterplot-ului seaborn de mai sus — aceleași date, API similar — dar output-ul e interactiv. Treci cu mouse-ul peste un punct și vezi toate coloanele din hover_data. Parametrul color= ia o coloană direct (categorică sau continuă) și plotly alege harta de culori. size= mapează o coloană numerică pe dimensiunea markerului.

Celelalte tipuri de grafic din plotly.express: px.line, px.bar, px.box, px.histogram, px.violin, px.heatmap, px.choropleth (geografic), px.scatter_3d. Pentru majoritatea cazurilor analitice nu ai nevoie de nimic sub acest strat.

Stratul de nivel scăzut e plotly.graph_objects, unde construiești figuri compunând trace-uri:

import plotly.graph_objects as go

fig = go.Figure()
fig.add_trace(go.Scatter(
    x=tips["total_bill"], y=tips["tip"],
    mode="markers",
    marker=dict(color="steelblue", size=8),
    name="tips",
))
fig.update_layout(title="Tips", xaxis_title="Total bill", yaxis_title="Tip")
fig.show()

Cobori la graph_objects când ai nevoie de o formă de grafic pe care express n-o are, sau când compui mai multe tipuri de trace-uri într-o singură figură (un candlestick cu medii mobile suprapuse, de exemplu), sau când încorporezi plotly într-un callback Dash care actualizează bucăți din figură.

Când să apelezi la plotly: orice e interactiv. Dashboard-uri (Streamlit și Dash randează amândouă plotly nativ). Rapoarte care trăiesc pe web și nu într-un PDF. Notebook-uri unde vrei ca cititorul să poată explora. Serii temporale cu mii de puncte unde zoom-ul e feature-ul real. Plotly e pe versiunea 5.x în 2026, matur și stabil; API-ul nu s-a agitat prea mult în ultimii ani.

Același grafic, de trei ori

Ca să vezi diferența, iată un scatter cu total_bill versus tip, codat prin culoare după ora din zi, scris în trei feluri:

# matplotlib: explicit, low-level
fig, ax = plt.subplots(figsize=(8, 5))
for time, group in tips.groupby("time"):
    ax.scatter(group["total_bill"], group["tip"], label=time, alpha=0.7)
ax.set_xlabel("Total bill")
ax.set_ylabel("Tip")
ax.legend(title="Time")
ax.set_title("Tips vs bill")

# seaborn: one line, looks right by default
sns.scatterplot(data=tips, x="total_bill", y="tip", hue="time")

# plotly: one line, plus interactivity
px.scatter(tips, x="total_bill", y="tip", color="time", title="Tips vs bill").show()

Toate trei randează aceleași date. Versiunea matplotlib îți dă control total și te obligă să scrii singur logica legendei. Versiunea seaborn e cea mai scurtă cale către un grafic static publicabil. Versiunea plotly e cea mai scurtă cale către un grafic pe care îl poți pune pe o pagină web.

Salvarea figurilor, DPI și câteva note practice

Pentru matplotlib și seaborn (care e matplotlib pe dedesubt), același savefig funcționează:

fig.savefig("output.png", dpi=300, bbox_inches="tight")
fig.savefig("output.pdf", bbox_inches="tight")  # vector, scales perfectly
fig.savefig("output.svg", bbox_inches="tight")

dpi=300 e valoarea implicită corectă pentru orice intră într-un raport tipărit sau o prezentare. bbox_inches="tight" taie spațiul alb din jurul figurii, ceea ce default-ul nu face. Pentru PDF, dpi nu contează — formatele vectoriale sunt independente de rezoluție.

Pentru plotly: fig.write_html("output.html") pentru versiunea interactivă, sau fig.write_image("output.png", scale=2) pentru static (asta cere pachetul kaleido; instalează cu uv add kaleido).

Dimensiunea figurii în matplotlib e în inci (figsize=(8, 5) înseamnă 8 inci lățime, 5 înălțime). Pentru un notebook Jupyter, 8x5 sau 10x6 se citesc bine. Pentru un dashboard Streamlit, dimensiunea implicită a plotly de obicei merge.

Câteva lucruri care îi mușcă pe oameni

Trei capcane care merită semnalate fiindcă îi costă pe toți o oră la un moment dat.

plt.show() se comportă diferit în scripturi și în notebook-uri. Într-un notebook Jupyter cu %matplotlib inline (default-ul), figurile randează automat când o celulă se termină; plt.show() e un no-op. Într-un script .py rulat din terminal, plt.show() e cel care blochează și deschide fereastra. Într-un context non-interactiv (un job de CI, un server) nu vrei deloc o fereastră — setează backend-ul pe Agg înainte să imporți pyplot: matplotlib.use("Agg") și apelează fig.savefig pe disc.

Axele de tip datetime au nevoie de formatare explicită în matplotlib. Când plotezi o pandas Series cu un DatetimeIndex, matplotlib face ce trebuie cam în 80% din cazuri. Restul de 20% obții etichete de dată suprapuse care au nevoie de fig.autofmt_xdate() sau de un mdates.DateFormatter personalizat. Seaborn și plotly tind să gestioneze asta mai grațios din cutie.

fig.show() din plotly nu apare mereu în orice mediu. Într-un notebook se randează inline. Într-un REPL Python obișnuit încearcă să deschidă un tab de browser. În Streamlit nu apelezi deloc fig.show() — apelezi st.plotly_chart(fig). În Dash, intră într-un dcc.Graph(figure=fig). Cunoașterea țintei de randare înainte să scrii codul îți salvează o sesiune de debugging confuză.

Alegerea uneia

Arborele de decizie, de la un capăt la altul:

  • Grafic static pentru o lucrare, slide sau raport PDF? Matplotlib (sau seaborn pentru un start mai rapid, apoi coboară la matplotlib pentru ajustări).
  • Grafic analitic rapid într-un notebook? Seaborn. O linie, arată corect, mergi mai departe.
  • Grafic interactiv pentru o aplicație web sau HTML partajabil? Plotly Express.
  • Dashboard? Plotly în Streamlit sau Dash.

Vei termina cu toate trei instalate în orice proiect serios de date. Nu-i nicio rușine să le amesteci: un notebook care construiește o analiză cu seaborn și se termină cu un grafic plotly șlefuit pentru rezumatul executiv e un artefact perfect normal în 2026.

O notă despre ce am lăsat afară deliberat: Altair (declarativ, bazat pe Vega-Lite, încântător pentru grafică statistică, dar comunitate mai mică), Bokeh (bibliotecă interactivă mai veche pe care plotly a înlocuit-o în mare pentru proiecte noi) și HoloViews (wrapper de nivel înalt care se compilează fie la Bokeh, fie la matplotlib). Sunt toate alegeri bune — Altair în special are o urmărire devotată — dar dacă pornești de la zero în 2026, trio-ul matplotlib/seaborn/plotly acoperă peste 95% din ce vei avea nevoie și se potrivește cu ce știu deja colegii tăi.

Următoarea lecție, SciPy, e ultima piesă a fundației numerice înainte să trecem în Modulul 9 și să începem să aplicăm acest stack la munca reală de ML și analiză.


Referință: documentația matplotlib, documentația seaborn, documentația Plotly Python, consultat 2026-05-01.

Caută