Python Viz 3 — Seaborn & vẽ nhanh từ DataFrame

13 thg 7, 2026 3 lượt xem
#python
#pandas
#seaborn
#statistical-visualization

Bài 2 — Matplotlib bạn đã có toàn quyền kiểm soát từng Artist, nhưng cái giá là mỗi biểu đồ phải dựng thủ công nhiều dòng. Khi công việc chuyển sang khám phá dữ liệu (EDA — Exploratory Data Analysis) — cần xem nhanh phân phối, so sánh nhóm, dò tương quan trên một DataFrame — thì gõ matplotlib thuần trở nên chậm và ồn ào. Đây đúng là mảnh đất của Seaborn: một thư viện cấp cao xây trên matplotlib, hiểu trực tiếp DataFrame, đẹp mặc định, và đặc biệt mạnh ở trực quan hoá thống kê (statistical visualization) — nghĩa là nó tự tính toán (trung bình, khoảng tin cậy, KDE, hồi quy) và vẽ luôn, thay vì bắt bạn tự tính rồi mới vẽ.

Bài này trả lời ba câu hỏi thực chiến: seaborn có những nhóm biểu đồ nào và dùng khi nào; sự khác biệt cốt lõi figure-level vs axes-level (nguồn gây rối lớn nhất cho người mới); và khi nào nên dùng .plot() của pandas/Polars để vẽ nhanh thay vì gọi seaborn.

Vì sao seaborn: tidy data + mặc định đẹp

Seaborn được thiết kế quanh khái niệm tidy data (dữ liệu gọn gàng): mỗi biến là một cột, mỗi quan sát là một hàng. Với dữ liệu ở dạng này, bạn không "vẽ x và y" theo kiểu truyền mảng, mà mô tả ý nghĩa: cột nào lên trục x, cột nào lên trục y, cột nào dùng để tô màu phân nhóm. Seaborn lo phần còn lại.

import seaborn as sns
import pandas as pd

sns.set_theme(style="whitegrid")   # bật theme đẹp mặc định cho toàn phiên
df = pd.read_parquet("accounts.parquet")

# Chỉ một dòng: seaborn tự nhóm theo city, vẽ box, xử lý màu
sns.boxplot(data=df, x="city", y="balance", hue="currency")

So với matplotlib thuần, ba việc được làm sẵn: (1) tự nhóm dữ liệu theo city/currency; (2) tự đặt nhãn trục bằng tên cột; (3) tự chọn bảng màu và style dễ nhìn. set_theme() áp một loạt rcParams (lưới nhạt, font, palette) khiến biểu đồ trông chuyên nghiệp mà không cần chỉnh tay.

Điểm mấu chốt cần nhớ ngay: mỗi hàm seaborn cuối cùng vẫn trả về/điều khiển đối tượng matplotlib. Bạn luôn có thể lấy ax ra chỉnh tiếp (định dạng tiền tệ, xoay nhãn, tiêu đề) bằng kỹ thuật ở Bài 2. Seaborn không thay thế matplotlib — nó là lớp tiện lợi phía trên.

Một hệ quả thực dụng của thiết kế "hiểu DataFrame": bạn thao tác trên tên cột, không trên mảng số. Điều này làm code gần với ngôn ngữ nghiệp vụ hơn (x="city", y="balance" đọc như một câu), giảm sai sót do truyền nhầm thứ tự mảng, và khiến việc đổi chiều phân tích chỉ là đổi tên một tham số. Với dữ liệu ngân hàng nhiều cột đặc trưng, đây là khác biệt đáng kể về tốc độ lặp khi khám phá.

Bốn nhóm biểu đồ theo mục đích

Cách gọn nhất để nhớ API seaborn là phân theo câu hỏi phân tích bạn đang đặt ra, chứ không theo tên hàm.

1. Quan hệ (relational) — hai biến số liên hệ thế nào?

  • scatterplot — điểm rời rạc, xem tương quan/cụm.
  • lineplot — chuỗi theo thời gian; nếu có nhiều giá trị y cho một x, seaborn tự tính trung bình và vẽ dải khoảng tin cậy 95%.
  • relplot — bản figure-level gói cả hai (chọn qua kind="scatter"|"line").

2. Phân phối (distribution) — một biến trải ra sao?

  • histplot — histogram (có thể chồng nhiều nhóm, chuẩn hoá thành mật độ).
  • kdeplot — đường mật độ mượt (Kernel Density Estimate).
  • ecdfplot — hàm phân phối tích luỹ, rất hợp để so sánh phân phối giữa các nhóm mà không lệ thuộc chọn bin.
  • displot — figure-level gói cả ba (kind="hist"|"kde"|"ecdf").

3. Phân loại (categorical) — so sánh giá trị số giữa các hạng mục.

  • boxplot — tóm tắt 5 số (min, Q1, median, Q3, max) + outlier; công cụ EDA số một để dò bất thường.
  • violinplot — box + hình dạng phân phối (KDE hai bên).
  • barplot — trung bình (mặc định) kèm thanh sai số; không phải tổng — dễ nhầm.
  • countplot — đếm số bản ghi mỗi hạng mục (giống value_counts vẽ ra).
  • stripplot/swarmplot — từng điểm một; swarm né chồng lấn, hợp khi ít điểm.
  • catplot — figure-level gói tất cả (kind="box"|"violin"|"bar"|"count"|"strip"|"swarm").

4. Ma trận & hồi quy.

  • heatmap — bảng màu, kinh điển cho ma trận tương quan (df.corr()).
  • clustermap — heatmap kèm phân cụm phân cấp, tự nhóm hàng/cột giống nhau lại gần nhau.
  • regplot/lmplot — scatter kèm đường hồi quy tuyến tính và dải tin cậy.
  • pairplot — vẽ ma trận mọi cặp biến số (scatter ngoài đường chéo, histogram trên đường chéo); một lệnh để "nhìn tổng thể" bộ dữ liệu.

Figure-level vs axes-level — điểm gây rối cốt lõi

Đây là khác biệt quan trọng nhất phải hiểu để không "đánh vật" với seaborn.

Axes-level (scatterplot, boxplot, histplot, kdeplot...) vẽ vào một Axes matplotlib. Chúng nhận tham số ax= và cư xử như một hàm matplotlib bình thường — bạn tự quản lý Figure, tự ghép subplot. Trả về một Axes.

Figure-level (relplot, catplot, displot, lmplot, pairplot, jointplot) quản lý toàn bộ Figure. Chúng trả về đối tượng FacetGrid (không phải Axes), và khả năng đắt giá nhất của chúng là faceting: chia dữ liệu thành lưới subplot theo row=/col= để so sánh nhóm cạnh nhau.

# Axes-level: bạn kiểm soát Figure, có thể nhét vào subplot tự dựng
fig, ax = plt.subplots(figsize=(8, 5))
sns.boxplot(data=df, x="city", y="balance", ax=ax)

# Figure-level: seaborn dựng cả lưới; mỗi currency một cột riêng
sns.catplot(data=df, x="city", y="balance",
            col="currency", kind="box", height=4, aspect=1.2)
Tiêu chíAxes-levelFigure-level
Trả vềmatplotlib.axes.AxesFacetGrid
Tham số ax=Không
Faceting row/colKhông
Ghép vào subplot tự dựngĐượcKhông nên
Chỉnh kích thướcfigsizeheight + aspect
Khi nào dùngGhép hình phức tạp, kiểm soát chi tiết, một panelKhám phá nhanh, so sánh nhiều nhóm

Quy tắc thực dụng: khám phá thì dùng figure-level (đặc biệt để facet); khi dựng hình cho báo cáo cần ghép nhiều panel khác loại và tinh chỉnh từng cái, dùng axes-level vì nó bỏ được vào subplots() do bạn quản lý. Nhầm lẫn kinh điển là gọi sns.relplot(..., ax=my_ax) — figure-level không nhận ax, sẽ báo lỗi.

Faceting là lý do chính người ta chịu học figure-level. Thay vì vẽ tám biểu đồ rời rồi ghép tay, bạn viết col="currency", row="segment" và seaborn tự chia lưới, đồng bộ trục, gắn tiêu đề từng ô — cực nhanh để so sánh cùng một câu hỏi qua nhiều nhóm. Kèm col_wrap= để cuộn lưới nhiều cột xuống hàng mới khi số nhóm lớn. Nhược điểm: bạn mất một phần quyền tinh chỉnh từng ô, và muốn chạm vào từng Axes con phải đi qua thuộc tính .axes của FacetGrid.

hue / size / style: mã hoá thêm chiều dữ liệu

Sức mạnh biểu đạt của seaborn nằm ở ba tham số "ngữ nghĩa" (semantic mappings) — mỗi cái đưa một cột thành một chiều thị giác:

  • hue= — màu sắc phân nhóm (biến hạng mục hoặc số).
  • size= — kích thước điểm (nhấn độ lớn của một biến số).
  • style= — kiểu marker/nét (dấu chấm tròn/vuông, nét liền/đứt).
sns.relplot(data=tx, x="created_at", y="amount",
            hue="kind", size="amount", style="kind", kind="scatter")

Một biểu đồ có thể tải được x, y, hue, size, style — tức 5 biến cùng lúc — nhưng đừng lạm dụng: quá 3 chiều thị giác thì người xem không đọc nổi. Với hue số liên tục, seaborn dùng palette tuần tự (sequential) và hiện thanh màu (colorbar); với hue hạng mục, nó dùng palette rời rạc (qualitative). Chọn đúng loại palette quan trọng cho tính trung thực — xem thêm Chọn biểu đồ.

Về theme và palette: sns.set_theme(style=..., palette=...) áp toàn cục; styledarkgrid/whitegrid/dark/white/ticks. Palette an toàn cho dữ liệu tuần tự là "viridis"/"rocket" (đều màu về mặt cảm nhận, thân thiện với người mù màu); tránh cầu vồng jet. Với thương hiệu ngân hàng, định nghĩa palette riêng bằng danh sách mã màu hex của NCB rồi truyền vào palette=.

Vẽ nhanh từ pandas / Polars: .plot()

Đôi khi bạn còn không cần seaborn. Cả pandas và Polars đều có .plot() gọi thẳng matplotlib — nhanh nhất cho một cái liếc mắt trong lúc phân tích.

# pandas: một dòng ra ngay biểu đồ
df["balance"].plot(kind="hist", bins=50)
df.groupby("city")["balance"].mean().plot(kind="bar")

# Polars: .plot dựa trên Altair (grammar-of-graphics), trả biểu đồ tương tác
import polars as pl
ldf = pl.read_parquet("accounts.parquet")
ldf.group_by("currency").agg(pl.col("balance").sum()).plot.bar(x="currency", y="balance")

Khác biệt cần biết: .plot() của pandas vẽ ra matplotlib tĩnh; .plot của Polars lại dựng trên Altair nên ra biểu đồ tương tác (xem PolarsAltair). Cả hai đều tối ưu cho tốc độ gõ, không cho tuỳ biến sâu.

Bảng quyết định gọn — khi nào dùng gì:

Tình huốngCông cụ
Liếc nhanh 1 cột/1 nhóm trong lúc codedf.plot()
EDA thống kê, cần box/violin/KDE/facet đẹpseaborn
Biểu đồ báo cáo chuẩn thương hiệu, kiểm soát pixelmatplotlib thuần
Cần zoom/hover/tương tác webplotly / Altair — xem Tổng quan

Ví dụ EEDA: ba biểu đồ chủ lực (minh hoạ)

Giả sử đã nạp accountstransactions vào DataFrame. Ba đoạn dưới là minh hoạ code Python (không phải SQL sandbox):

import seaborn as sns, matplotlib.pyplot as plt
sns.set_theme(style="whitegrid")

# 1) Phân phối số dư theo thành phố — boxplot để lộ outlier & lệch phải
plt.figure(figsize=(10, 5))
ax = sns.boxplot(data=acc, x="city", y="balance", showfliers=True)
ax.set_yscale("log")                       # số dư lệch phải rất mạnh -> log
ax.set_ylabel("Số dư (VND, thang log)")
ax.tick_params(axis="x", rotation=30)

# 2) Heatmap tương quan giữa các đặc trưng số của khách hàng
feat = cust[["balance", "n_tx_30d", "avg_amount", "tenure_days"]]
corr = feat.corr(numeric_only=True)
sns.heatmap(corr, annot=True, fmt=".2f", cmap="rocket_r",
            vmin=-1, vmax=1, square=True)

# 3) Faceting theo currency — mỗi loại tiền một panel histogram riêng
sns.displot(data=acc, x="balance", col="currency",
            kind="hist", bins=40, log_scale=True, height=4, facet_kws={"sharey": False})

Ba lựa chọn thiết kế đáng chú ý: thang log cho số dư (dữ liệu tài chính hầu như luôn lệch phải, không log thì cả trăm khách nhỏ dồn thành một vạch); vmin=-1, vmax=1 cố định thang màu heatmap tương quan để màu có ý nghĩa nhất quán; sharey=False khi facet để mỗi loại tiền có trục riêng (số dư USD và VND lệch nhau hàng nghìn lần).

Nếu cần truy dữ liệu nguồn cho các biểu đồ trên, câu tổng hợp số dư theo thành phố dưới đây chạy được trực tiếp trên sandbox Postgres:

-- ▶ Chạy được
SELECT c.city,
       COUNT(*)                          AS so_tk,
       ROUND(AVG(a.balance)::numeric, 2) AS so_du_tb,
       ROUND(PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY a.balance)::numeric, 2) AS trung_vi
FROM accounts a
JOIN customers c ON c.id = a.customer_id
GROUP BY c.city
ORDER BY so_du_tb DESC;

Kết quả city, so_du_tb, trung_vi đưa thẳng vào sns.barplot(data=res, x="city", y="so_du_tb"). Lưu ý theo schema sandbox: currencybalance nằm ở bảng accounts, còn tên/thành phố khách phải JOIN từ customers (không có sẵn ở accounts).

Tiếng Việt, định dạng tiền & dữ liệu lớn

Font tiếng Việt. Seaborn kế thừa font của matplotlib. Nếu dấu tiếng Việt bị vuông/mất, đặt font hỗ trợ Unicode đầy đủ (ví dụ DejaVu Sans đã có sẵn, hoặc Noto Sans) qua plt.rcParams["font.family"] trước khi vẽ.

Định dạng số tiền. Seaborn không tự format trục theo VND. Vì mỗi hàm trả Axes, bạn định dạng bằng matplotlib như Bài 2:

from matplotlib.ticker import FuncFormatter
ax.yaxis.set_major_formatter(FuncFormatter(lambda v, _: f"{v/1e6:,.0f} tr"))

Dữ liệu lớn. Đây là bẫy hiệu năng lớn nhất. scatterplot/swarmplot với hàng triệu điểm sẽ treo máy và ra một khối mực đen vô nghĩa. Ba cách xử lý:

  • Lấy mẫu (sample) trước khi vẽ: df.sample(50_000) — với EDA phân phối, vài chục nghìn điểm là quá đủ để thấy hình dạng.
  • Dùng biểu đồ tổng hợp thay vì từng điểm: histplot, kdeplot, boxplot tóm tắt hàng triệu dòng thành vài con số, chạy nhanh và đọc rõ hơn.
  • Với scatter dày đặc, dùng histplot 2D hoặc kdeplot hai biến (x=, y=) thay cho scatterplot.

Mẹo: hãy tổng hợp bằng Polars/SQL trước, chỉ đưa dữ liệu đã gom vào seaborn. Vẽ 20 dòng đã GROUP BY luôn tốt hơn ném 20 triệu dòng thô cho thư viện vẽ.

Use case thực tế

Bối cảnh (số liệu ước lượng, minh hoạ). Tổ Phân tích Dữ liệu NCB nhận yêu cầu: trước khi làm báo cáo quý về cấu trúc số dư tiền gửi, cần một vòng EDA nhanh để (a) phát hiện bất thường và (b) phân nhóm sơ bộ khách hàng — làm nền cho báo cáo chính thức. Dữ liệu: ~1,2 triệu tài khoản, cột city, currency, balance, và đặc trưng hành vi 30 ngày.

Quy trình nửa buổi:

  1. Tổng hợp trước bằng Polars/SQL. Gom AVG, trung vị, phân vị 90/99 số dư theo city × currency — từ 1,2 triệu dòng còn ~120 dòng. Toàn bộ nặng nề đẩy về tầng dữ liệu, seaborn chỉ nhận bảng nhỏ.

  2. Boxplot phân phối số dư theo thành phố (thang log). Ngay lập tức lộ ra: một chi nhánh có cụm outlier cao bất thường ở phân vị 99 — khoảng 40 tài khoản số dư gấp hơn 50 lần trung vị. Đánh dấu để nghiệp vụ soi (có thể là tài khoản doanh nghiệp gắn nhầm phân khúc cá nhân, hoặc dấu hiệu cần rà theo AML).

  3. Faceting displot theo currency. Mỗi loại tiền một panel histogram với trục riêng (sharey=False). Panel USD cho thấy phân phối hai đỉnh (bimodal) — gợi ý tồn tại hai nhóm khách rõ rệt: gửi ngoại tệ nhỏ lẻ và nhóm số dư lớn — đầu vào tốt cho phân khúc.

  4. Heatmap tương quan giữa balance, số giao dịch 30 ngày, giá trị giao dịch trung bình, thời gian gắn bó. Tương quan balanceavg_amount cao (~0,6) trong khi balancen_tx_30d thấp, hàm ý: khách số dư lớn giao dịch giá trị lớn nhưng thưa — thông tin định hình cách phân nhóm.

Kết quả. Chỉ với ~30 dòng seaborn, tổ phân tích: chốt được 1 bất thường cần điều tra, 1 giả thuyết phân khúc (bimodal USD), và 1 insight tương quan — tất cả trước khi động vào slide báo cáo. Các con số cụ thể ở trên là ước lượng minh hoạ cho quy trình, không phải số liệu thật.

Ghi nhớ

  • Seaborn = lớp cấp cao trên matplotlib, hiểu tidy DataFrame, đẹp mặc định; mạnh nhất ở trực quan hoá thống kê (tự tính KDE, CI, hồi quy).
  • Nhớ 4 nhóm theo mục đích: quan hệ (scatter/line/relplot), phân phối (hist/kde/ecdf/displot), phân loại (box/violin/bar/count/strip/swarm/catplot), ma trận (heatmap/clustermap) + regplot/pairplot.
  • Figure-level (relplot/catplot/displot/lmplot/pairplot) trả FacetGrid, làm được faceting row/col, không nhận ax=. Axes-level trả Axes, nhận ax=, hợp để ghép hình. Nhầm là gọi relplot(ax=...) — lỗi.
  • hue/size/style mã hoá thêm chiều; đừng quá 3 chiều thị giác. Palette tuần tự (viridis/rocket) cho biến số, rời rạc cho hạng mục; tránh cầu vồng.
  • sns.set_theme() bật style/palette toàn cục; mỗi hàm vẫn trả Axes để bạn định dạng tiền tệ/tiếng Việt bằng matplotlib.
  • Vẽ nhanh: df.plot() (pandas → matplotlib tĩnh), ldf.plot (Polars → Altair tương tác). Dùng để liếc, không để tuỳ biến sâu.
  • Dữ liệu lớn: lấy mẫu hoặc dùng biểu đồ tổng hợp; tốt nhất là GROUP BY trước rồi mới vẽ. Số tài chính lệch phải → cân nhắc thang log.
  • Chọn công cụ: liếc nhanh → .plot(); EDA thống kê → seaborn; báo cáo chuẩn thương hiệu → matplotlib; tương tác web → plotly/Altair.

Nguồn tham khảo

Bài viết liên quan

Vì sao Python là ngôn ngữ số một của data engineer: vai trò trong pipeline (ingest/transform/orchestrate), hệ sinh thái thư viện (pandas/polars/pyarrow/sqlalchemy), quản lý môi trường (venv/uv/poetry), và khi nào dùng Python vs SQL/Spark.

13 thg 7, 2026 6

Biến script thành pipeline đáng tin cậy: cấu trúc project & packaging (uv/poetry), type hints & pydantic, kiểm thử với pytest, logging & cấu hình, đóng gói Docker, và tích hợp CI cho code dữ liệu.

13 thg 7, 2026 5

Học cách tổ chức code Python: định nghĩa hàm với tham số vị trí/từ khoá/mặc định, *args/**kwargs, lambda và hàm bậc cao, closure, decorator, generator với yield. Đóng gói code thành module và package, cô lập thư viện bằng môi trường ảo venv, quản lý phụ thuộc với pip và requirements.txt để dự án tái lập được trên mọi máy.

13 thg 7, 2026 5

Hướng dẫn OOP trong Python từ class/instance, kế thừa và super(), đa hình & duck typing, encapsulation tới dunder methods, @property, classmethod/staticmethod, dataclass và type hints (mypy). Kèm nguyên tắc clean code: đặt tên rõ nghĩa, hàm nhỏ, DRY, SOLID cùng chuẩn PEP8 với công cụ ruff/black.

13 thg 7, 2026 5

Cảm nhận của bạn

Bình luận

Bạn cần để viết bình luận.

Chưa có bình luận. Hãy là người đầu tiên chia sẻ!