Python Viz 8 — Thực chiến trực quan hoá báo cáo ngân hàng
Từ bảy công cụ rời rạc đến một quy trình
Bảy bài trước của series lần lượt mổ xẻ từng công cụ: bức tranh tổng quan, matplotlib cho biểu đồ tĩnh kiểm soát tuyệt đối, seaborn/pandas cho khám phá nhanh, plotly và altair cho biểu đồ tương tác, folium cho bản đồ, và Streamlit cho app tự phục vụ. Mỗi công cụ giỏi một việc. Nhưng trong công việc thật, không ai vẽ một biểu đồ rồi thôi — bạn phải sản xuất một bộ báo cáo, tái lập hằng tháng, đúng chuẩn thương hiệu, tự động, không sai số.
Bài này là bài thực chiến khép series: ghép tất cả thành một quy trình báo cáo trực quan end-to-end, đi từ dữ liệu thô đến sản phẩm giao cho ban lãnh đạo. Trọng tâm không phải "vẽ đẹp" mà là "vẽ đúng, tái lập được, và không tốn công thủ công mỗi kỳ".
Quy trình báo cáo end-to-end
Một báo cáo trực quan chuyên nghiệp đi qua bốn chặng. Nhầm lẫn thường gặp là nhảy thẳng vào chặng vẽ mà bỏ qua chặng chuẩn bị — dẫn tới biểu đồ đẹp trên dữ liệu sai.
Chặng 1 — Lấy dữ liệu. Nguồn là kho dữ liệu (data warehouse/lakehouse). Với dữ liệu lớn, đừng kéo hàng triệu dòng giao dịch về máy rồi mới tổng hợp; hãy đẩy phép tổng hợp xuống tận nguồn bằng SQL, hoặc dùng Polars / DuckDB để tính lazy trên file Parquet. Nguyên tắc vàng: tổng hợp trước khi vẽ. Biểu đồ chỉ cần vài chục điểm, không cần triệu dòng.
Chặng 2 — Chuẩn bị. Ép kiểu ngày, xử lý null, chuẩn hoá đơn vị (đồng → tỷ đồng), tính chỉ số phái sinh (tỷ lệ CASA, tỷ lệ nợ xấu, tăng trưởng so kỳ). Đây là nơi định nghĩa chỉ số phải khớp với từ điển KPI để mọi báo cáo nói cùng một ngôn ngữ.
Chặng 3 — Chọn công cụ. Quyết định dựa trên người nhận và kênh phân phối, không phải sở thích. Bảng dưới là kim chỉ nam.
Chặng 4 — Xuất & phân phối. Báo cáo tĩnh in PDF nhúng email; dashboard tương tác xuất HTML nhúng portal; app self-service deploy Streamlit. Toàn bộ script nằm trong Git, chạy theo lịch (cron/Airflow), có version.
Chọn công cụ theo tình huống
Đây là bảng tra cứu cô đọng cả series — dán lên tường phòng phân tích:
| Tình huống | Công cụ | Vì sao |
|---|---|---|
| Báo cáo tĩnh chuẩn thương hiệu (in, PDF, slide) | matplotlib | Kiểm soát tuyệt đối từng pixel, font, màu; xuất vector/PNG DPI cao |
| Khám phá nhanh (EDA), phân phối, tương quan | seaborn / pandas.plot | Một dòng ra biểu đồ thống kê; hợp notebook |
| Dashboard tương tác, nhúng web, có hover/zoom | plotly / altair | HTML tương tác; altair mạnh về grammar khai báo |
| Bản đồ, phân bố địa lý chi nhánh/khách hàng | folium | Bản đồ nền, marker, choropleth theo tỉnh |
| App tự phục vụ cho người không code | Streamlit | Bộ lọc, tải file, cập nhật realtime, không cần frontend |
Quy tắc đơn giản: báo cáo giao cho lãnh đạo → tĩnh (matplotlib) vì cần kiểm soát bố cục và in ấn; công cụ để người khác tự khám phá → tương tác (plotly/Streamlit). Đừng gửi HTML tương tác 20MB vào email, và đừng bắt lãnh đạo bấm bộ lọc để thấy con số họ cần — số quan trọng phải nằm sẵn trên tiêu đề.
Best practices trực quan hoá ngân hàng
Ngành ngân hàng có yêu cầu khắt khe hơn một blog phân tích: số liệu ra quyết định tín dụng, thanh khoản, tuân thủ. Sáu nguyên tắc bắt buộc:
1. Nhất quán thương hiệu
Mọi biểu đồ NCB phải cùng bảng màu, cùng font, cùng vị trí logo. Đừng chỉnh màu thủ công từng lần — hãy đóng gói vào style/template tái dùng. Với matplotlib, tạo một file .mplstyle hoặc một hàm apply_ncb_style(); với plotly, dựng một template riêng.
# MINH HOẠ — style thương hiệu tái dùng cho matplotlib
import matplotlib.pyplot as plt
from matplotlib import cycler
NCB_COLORS = ["#B01C2E", "#1F3A5F", "#4A8DB7", "#E0A458", "#7A7A7A"]
def apply_ncb_style():
plt.rcParams.update({
"font.family": "DejaVu Sans",
"axes.prop_cycle": cycler(color=NCB_COLORS),
"axes.grid": True, "grid.alpha": 0.3,
"axes.spines.top": False, "axes.spines.right": False,
"figure.dpi": 150,
})
Gọi apply_ncb_style() một lần đầu script là mọi biểu đồ sau đó thống nhất. Đây chính là "tái lập" áp dụng cho hình thức, không chỉ số liệu.
2. Định dạng tiền tệ, tỷ lệ, ngày tháng
Trục hiển thị 1500000000 là thất bại giao tiếp. Ngân hàng làm việc bằng tỷ đồng, phần trăm hai chữ số, ngày theo chuẩn Việt Nam.
# MINH HOẠ — formatter tiền tệ và phần trăm cho matplotlib
from matplotlib.ticker import FuncFormatter
def ty_dong(x, _): # 1.5e9 -> "1,5 tỷ"
return f"{x/1e9:,.1f} tỷ".replace(",", " ").replace(".", ",")
def phan_tram(x, _):
return f"{x*100:.1f}%"
ax.yaxis.set_major_formatter(FuncFormatter(ty_dong))
3. Trung thực — không bóp méo trục
Đây là ranh giới đạo đức. Cắt trục Y không từ 0 để phóng đại một cú tăng nhỏ, dùng trục kép gây hiểu nhầm tương quan, hay chọn dải màu đánh lừa mắt — tất cả biến báo cáo thành công cụ thao túng. Trong ngân hàng, một biểu đồ nợ xấu bị "làm đẹp" có thể che giấu rủi ro thật. Nguyên tắc: trục giá trị bắt đầu từ 0 với biểu đồ cột; nếu buộc phải cắt để thấy chi tiết, ghi chú rõ. Chi tiết về thiết kế trung thực xem nguyên tắc thiết kế dashboard và chọn biểu đồ đúng.
4. Thông điệp rõ — tiêu đề là insight
Tiêu đề "Biểu đồ dư nợ theo tháng" vô dụng. Tiêu đề tốt là kết luận: "Dư nợ tăng 8,3% so cùng kỳ, dẫn dắt bởi phân khúc SME". Người đọc thấy ngay điều cần biết; biểu đồ chỉ để chứng minh. Mỗi biểu đồ trả lời đúng một câu hỏi.
5. Tái lập & tự động hoá
Không có bước thủ công nào được phép nằm giữa dữ liệu và báo cáo cuối. Nếu tháng sau phải copy-paste lại, quy trình đã hỏng. Toàn bộ pipeline là script, chạy được bằng một lệnh, lên lịch định kỳ, và mọi thay đổi có lịch sử trong Git.
6. Hiệu năng dữ liệu lớn
Đừng vẽ scatter 5 triệu điểm — mắt không đọc được và trình duyệt treo. Tổng hợp/lấy mẫu/binning trước. Một heatmap 12 tháng × 40 chi nhánh (480 ô) truyền tải nhiều hơn một scatter triệu điểm.
Bộ báo cáo KQKD điển hình của NCB
Báo cáo kết quả kinh doanh (KQKD) chi nhánh gồm bốn nhóm chỉ số cốt lõi. Mỗi chỉ số hợp một loại biểu đồ:
| Chỉ số | Câu hỏi | Biểu đồ nên dùng |
|---|---|---|
| Huy động vốn | Quy mô & tăng trưởng theo kỳ | Cột (so kỳ) + đường xu hướng |
| Dư nợ tín dụng | Cơ cấu theo phân khúc | Cột chồng hoặc cột nhóm |
| CASA (tỷ lệ tiền gửi không kỳ hạn) | Tỷ lệ theo chi nhánh | Cột ngang xếp hạng |
| Nợ xấu (NPL) | Diễn biến rủi ro theo thời gian | Đường + ngưỡng cảnh báo |
| Phân bố địa lý | Chi nhánh nào ở đâu, quy mô ra sao | Bản đồ folium (marker/choropleth) |
- Huy động & dư nợ: dùng cột so sánh kỳ này với cùng kỳ năm trước — mắt so chiều cao cột dễ hơn so số.
- CASA: cột ngang xếp hạng chi nhánh giúp thấy ngay ai dẫn đầu, ai đuối.
- Nợ xấu: biểu đồ đường với một đường ngưỡng (ví dụ 3%) để lập tức thấy chi nhánh nào vượt trần.
- Địa lý: bản đồ folium, kích thước marker tỷ lệ dư nợ — xem thêm trực quan hoá không gian.
Ví dụ xâu chuỗi: từ query đến biểu đồ có định dạng
Đây là "hình dạng" của một script báo cáo hoàn chỉnh, ghép các chặng lại (MINH HOẠ, rút gọn):
# MINH HOẠ — pipeline báo cáo: query -> Polars -> matplotlib có định dạng tiền tệ -> xuất
import polars as pl
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter
# --- Chặng 1: lấy dữ liệu (đọc từ warehouse ra DataFrame) ---
df = pl.read_database(query="""
SELECT date_trunc('month', created_at) AS thang,
SUM(amount) AS huy_dong
FROM transactions
WHERE kind = 'deposit'
GROUP BY 1 ORDER BY 1
""", connection=conn)
# --- Chặng 2: chuẩn bị (tính tăng trưởng so kỳ) ---
df = df.with_columns(
(pl.col("huy_dong").pct_change() * 100).round(1).alias("tang_truong")
)
# --- Chặng 3: vẽ (matplotlib, style thương hiệu) ---
apply_ncb_style()
fig, ax = plt.subplots(figsize=(9, 4.5))
ax.bar(df["thang"], df["huy_dong"], color="#B01C2E")
ax.yaxis.set_major_formatter(FuncFormatter(lambda x, _: f"{x/1e9:,.0f} tỷ"))
ax.set_title("Huy động vốn tăng đều, đỉnh vào cuối quý", loc="left", weight="bold")
# --- Chặng 4: xuất ---
fig.savefig("bao_cao/huy_dong_2026.pdf", bbox_inches="tight")
Điểm mấu chốt: bốn chặng nằm trong một script, chạy một lệnh ra file PDF chuẩn thương hiệu. Tháng sau đổi khoảng thời gian, chạy lại — không đụng tay vào biểu đồ.
Còn đây là một câu SQL nguồn chạy được trên sandbox, tổng hợp huy động theo tháng (đúng tinh thần "tổng hợp trước khi vẽ"):
-- ▶ Chạy được
SELECT date_trunc('month', created_at) AS thang,
COUNT(*) AS so_giao_dich,
ROUND(SUM(amount)::numeric, 2) AS tong_tien
FROM transactions
WHERE kind = 'deposit'
GROUP BY 1
ORDER BY 1;
Và một câu xếp hạng số dư theo loại tiền tệ — dữ liệu cho biểu đồ cột phân khúc:
-- ▶ Chạy được
SELECT currency,
COUNT(*) AS so_tai_khoan,
ROUND(AVG(balance)::numeric, 2) AS so_du_tb,
ROUND(SUM(balance)::numeric, 2) AS tong_so_du
FROM accounts
GROUP BY currency
ORDER BY tong_so_du DESC;
Cả hai câu trả về vài dòng — vừa đủ cho một biểu đồ, không kéo triệu dòng về máy.
Tự động hoá & phân phối
Sản xuất một biểu đồ đã dễ; thách thức thật là sản xuất hàng loạt báo cáo mỗi kỳ, tự động, có version.
- Sinh hàng loạt: một vòng lặp qua danh sách chi nhánh, mỗi vòng query dữ liệu chi nhánh đó, vẽ, xuất
bao_cao/chi_nhanh_{id}.pdf. 40 chi nhánh = 40 file trong vài giây, thay vì 40 lần copy-paste Excel. - PDF/HTML: matplotlib xuất PDF trực tiếp; báo cáo nhiều trang dùng
matplotlib.backends.backend_pdf.PdfPages. Dashboard plotly/altair xuất HTML tự chứa (fig.write_html) để nhúng portal nội bộ. - Nhúng email/portal: đính PDF vào email tự động, hoặc đẩy HTML lên portal. Ảnh PNG chèn thẳng thân email cho bản tóm tắt sáng.
- Lịch chạy: đưa script vào cron hoặc Airflow — báo cáo tự sinh mùng 1 hằng tháng, gửi trước giờ làm.
- Versioning: script trong Git (có lịch sử logic tính toán); file output đặt tên theo kỳ (
_2026_06) và lưu vào thư mục/kho có version để đối chiếu sau này.
Use case thực tế
Bối cảnh. Phòng Kế hoạch – Tài chính NCB mỗi tháng phải phát hành bộ báo cáo KQKD cho khoảng 40 chi nhánh: huy động, dư nợ, CASA, nợ xấu, kèm so sánh cùng kỳ. Quy trình cũ hoàn toàn thủ công trong Excel: một chuyên viên chạy nhiều query, copy kết quả vào các sheet mẫu, chỉnh biểu đồ bằng chuột, dán vào slide, lặp lại cho từng chi nhánh. Ước lượng nội bộ: mỗi kỳ tốn khoảng 2–3 ngày công, và tháng nào cũng có vài chi nhánh bị lệch định dạng hoặc dán nhầm số do thao tác tay.
Giải pháp. Team tái lập toàn bộ bằng Python theo đúng quy trình bốn chặng bài này:
- Lấy dữ liệu: một bộ query SQL tham số hoá theo
chi_nhanhvàky, tổng hợp ngay tại warehouse. - Chuẩn bị: Polars tính tăng trưởng so kỳ, tỷ lệ CASA, tỷ lệ NPL, chuẩn hoá về tỷ đồng.
- Vẽ: matplotlib với
apply_ncb_style()và formatter tiền tệ — cột huy động/dư nợ so kỳ, đường nợ xấu có ngưỡng 3%, cột ngang xếp hạng CASA; thêm một bản đồ folium tổng thể cho toàn hàng. - Xuất: vòng lặp qua 40 chi nhánh, mỗi chi nhánh một PDF nhiều trang qua
PdfPages, đặt tênKQKD_{chi_nhanh}_{ky}.pdf, đẩy lên portal và đính email cho giám đốc chi nhánh.
Kết quả (ước lượng). Thời gian phát hành từ 2–3 ngày công xuống dưới 30 phút (chủ yếu là chạy script và rà soát). Định dạng nhất quán tuyệt đối vì dùng chung style; hết lỗi dán nhầm số vì không còn thao tác tay. Script trong Git nên khi lãnh đạo hỏi "con số này tính thế nào", mở đúng dòng code là trả lời được. Từ kỳ thứ hai trở đi, cron tự chạy — người chỉ việc rà soát và bấm gửi.
Đây chính là lời hứa mở đầu series (vì sao vẽ bằng code) trở thành hiện thực: biểu đồ là hàm của dữ liệu, tái lập, tự động, không thủ công.
Ghi nhớ
- Báo cáo trực quan là quy trình bốn chặng: lấy dữ liệu → chuẩn bị → chọn công cụ → xuất/app. Đừng nhảy thẳng vào vẽ.
- Tổng hợp trước khi vẽ: đẩy phép tính xuống SQL/Polars/DuckDB; biểu đồ chỉ cần vài chục điểm.
- Chọn công cụ theo người nhận & kênh: báo cáo lãnh đạo → matplotlib tĩnh; công cụ tự khám phá → plotly/Streamlit; bản đồ → folium; EDA → seaborn.
- Nhất quán thương hiệu bằng style/template tái dùng, không chỉnh màu thủ công từng lần.
- Định dạng đúng: tỷ đồng, phần trăm, ngày Việt Nam — số thô trên trục là thất bại giao tiếp.
- Trung thực: trục cột bắt đầu từ 0, không bóp méo; báo cáo ngân hàng che rủi ro là vượt ranh giới đạo đức.
- Tiêu đề là insight, không phải nhãn — nói kết luận, biểu đồ chỉ chứng minh.
- Tự động hoá triệt để: một script, một lệnh, chạy theo lịch, version trong Git — nếu tháng sau phải copy-paste, quy trình đã hỏng.
Nguồn tham khảo
- Matplotlib Documentation — Style sheets & rcParams,
matplotlib.ticker(FuncFormatter), backend PDF (PdfPages). - Seaborn Documentation — Statistical data visualization cho EDA.
- Plotly Python Graphing Library — Biểu đồ tương tác, templates,
write_html. - Streamlit Documentation — Xây app dữ liệu tự phục vụ.
- Stephen Few — Show Me the Numbers: Designing Tables and Graphs to Enlighten (Analytics Press) — nguyên tắc trình bày bảng/biểu đồ báo cáo kinh doanh.
- Edward R. Tufte — The Visual Display of Quantitative Information (Graphics Press) — trung thực trục và data-ink ratio.
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.
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.
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.
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.
Cảm nhận của bạn
Bình luận
Chưa có bình luận. Hãy là người đầu tiên chia sẻ!