Appearance
🏆 Tiêu chuẩn — Visualization (Matplotlib & Seaborn)
Các tiêu chuẩn giúp bạn tạo biểu đồ rõ ràng, chuyên nghiệp, ai cũng đọc được — thay vì chart "đẹp mắt" nhưng gây hiểu lầm
Tổng quan tiêu chuẩn buổi 10
Buổi 10 là bước iNterpret trong hành trình OSEMN — biến insight từ EDA thành visual story mà stakeholder hiểu ngay. Nhưng "vẽ chart" không đồng nghĩa "communicate tốt". Một biểu đồ sai chart type, thừa trang trí, hay không accessibility-friendly có thể gây hiểu lầm hoặc loại trừ người xem. Theo nghiên cứu của Nielsen Norman Group (2024), 65% decision-makers đánh giá tính chuyên nghiệp của analyst qua chất lượng biểu đồ — trước cả nội dung phân tích.
Thiếu tiêu chuẩn visualization, DA thường mắc 3 sai lầm:
- Chart junk — thêm 3D effect, gradient, icon trang trí → che mất data thật
- Inconsistency — mỗi chart một style, màu sắc, font → report trông nghiệp dư
- Inaccessible — dùng red-green color scheme → ~8% nam giới (colorblind) không đọc được
Buổi này tập trung vào 3 tiêu chuẩn cốt lõi cho visualization:
- IBCS — Tiêu chuẩn quốc tế cho business chart, đảm bảo consistency toàn tổ chức
- Tufte's Principles — Nguyên tắc data-ink ratio và chart declutter từ Edward Tufte
- Accessibility Standards — Thiết kế chart cho mọi người, bao gồm người khiếm thị màu
📋 Danh sách tiêu chuẩn liên quan
| # | Tiêu chuẩn | Tổ chức / Tác giả | Áp dụng cho Buổi 10 |
|---|---|---|---|
| 1 | IBCS | International Business Communication Standards | Chart standards quốc tế — notation, layout, structure |
| 2 | Tufte's Principles | Edward Tufte (1983–2006) | Data-ink ratio, chart declutter, small multiples |
| 3 | Accessibility Standards | W3C / WCAG + Cộng đồng Data Viz | Colorblind-safe design, contrast, alt-text |
1️⃣ IBCS — International Business Communication Standards
Giới thiệu
IBCS (International Business Communication Standards) là bộ tiêu chuẩn quốc tế do Dr. Rolf Hichert khởi xướng từ năm 2004, nhằm tạo ra "ngôn ngữ chung" cho business charts — giống như IFRS là chuẩn chung cho kế toán. IBCS được áp dụng bởi các tập đoàn lớn như SAP, Siemens, Swiss Re, Bosch và đang trở thành tiêu chuẩn phổ biến nhất cho báo cáo doanh nghiệp.
Ý tưởng cốt lõi: Mọi biểu đồ trong tổ chức phải "nói cùng ngôn ngữ" — khi nhìn column đen, ai cũng biết đó là Actual; column trắng viền đen là Plan/Budget. Không cần legend giải thích.
IBCS tuân theo nguyên tắc SUCCESS:
S — Say → Truyền đạt thông điệp rõ ràng
U — Unify → Thống nhất notation qua toàn bộ report
C — Condense → Cô đọng — nhiều thông tin trong ít không gian
C — Check → Kiểm tra tính chính xác
E — Express → Chọn đúng chart type cho đúng data
S — Simplify → Loại bỏ trang trí thừa
S — Structure → Cấu trúc layout hợp lýÁp dụng trong buổi học
IBCS Notation — Quy ước màu sắc & pattern
| Ký hiệu | Ý nghĩa | Matplotlib Code |
|---|---|---|
| ██ Đen (solid) | Actual — số liệu thực tế | color='black' |
| ░░ Xám nhạt | Previous Year — kỳ trước | color='#B0B0B0' |
| ▢▢ Trắng viền đen | Plan / Budget | facecolor='white', edgecolor='black' |
| ▨▨ Gạch chéo | Forecast — dự báo | hatch='///', facecolor='white' |
| 🔺 Tam giác xanh | Positive variance (thuận lợi) | color='#2E7D32' (green) |
| 🔻 Tam giác đỏ | Negative variance (bất lợi) | color='#C62828' (red) |
Ví dụ — IBCS Bar Chart: Actual vs Plan
python
import matplotlib.pyplot as plt
import numpy as np
# Data: Doanh thu 4 quý
quarters = ["Q1", "Q2", "Q3", "Q4"]
actual = [42, 48, 45, 52] # Actual — đen
plan = [40, 50, 47, 55] # Plan — trắng viền đen
variance = [a - p for a, p in zip(actual, plan)] # Variance
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5),
gridspec_kw={"width_ratios": [3, 1]})
# --- Chart 1: Actual vs Plan (IBCS notation) ---
x = np.arange(len(quarters))
width = 0.35
# Plan bars — trắng viền đen (IBCS)
ax1.bar(x - width/2, plan, width, label="Plan",
facecolor="white", edgecolor="black", linewidth=1.5)
# Actual bars — đen (IBCS)
ax1.bar(x + width/2, actual, width, label="Actual",
color="black")
ax1.set_xlabel("Quarter")
ax1.set_ylabel("Revenue (Billion VND)")
ax1.set_title("Revenue: Actual vs Plan — FY2025", fontsize=13, fontweight="bold")
ax1.set_xticks(x)
ax1.set_xticklabels(quarters)
ax1.legend(frameon=False)
ax1.spines[["top", "right"]].set_visible(False) # IBCS: bỏ border thừa
# --- Chart 2: Variance (IBCS: green/red) ---
colors = ["#2E7D32" if v >= 0 else "#C62828" for v in variance]
ax2.barh(quarters, variance, color=colors, edgecolor="none")
ax2.set_xlabel("Variance (B VND)")
ax2.set_title("ΔPlan", fontsize=13, fontweight="bold")
ax2.axvline(0, color="black", linewidth=0.8)
ax2.spines[["top", "right"]].set_visible(False)
plt.tight_layout()
plt.savefig("ibcs_actual_vs_plan.png", dpi=150, bbox_inches="tight")
plt.show()💡 IBCS = consistency > creativity
Trong business reporting, consistency quan trọng hơn sáng tạo. Khi cả team dùng chung IBCS notation, sếp nhìn chart Q1 hay Q4, Việt Nam hay Singapore, đều hiểu ngay — zero learning curve. Matplotlib hoàn toàn có thể tuân thủ IBCS qua custom colors, hatches, và styles.
Checklist IBCS
- [ ] Notation thống nhất — Actual = đen, Plan = trắng viền đen, Forecast = gạch chéo
- [ ] Variance chart — luôn đi kèm main chart, green = tốt, red = xấu
- [ ] Không 3D effect — tất cả chart 2D flat
- [ ] Bỏ border thừa —
spines[["top", "right"]].set_visible(False) - [ ] Label trực tiếp — data label trên bar thay vì phụ thuộc legend
- [ ] Consistent scale — cùng metric phải cùng scale trên các chart khác nhau
- [ ] Title = message — title chart nêu insight, không chỉ mô tả ("Revenue grew 12%" thay vì "Revenue Chart")
2️⃣ Tufte's Principles — Data-Ink Ratio & Chart Declutter
Giới thiệu
Edward Tufte là giáo sư Yale và được gọi là "Leonardo da Vinci of Data" — tác giả 4 cuốn sách kinh điển về data visualization, bắt đầu từ "The Visual Display of Quantitative Information" (1983). Nguyên tắc cốt lõi của Tufte: mỗi giọt mực trên biểu đồ phải truyền tải thông tin — nếu không, xóa đi.
Tufte giới thiệu khái niệm Data-Ink Ratio:
Mục tiêu: Data-Ink Ratio càng gần 1.0 càng tốt — nghĩa là mọi thứ trên chart đều phục vụ data, không có trang trí thừa.
Áp dụng trong buổi học
5 nguyên tắc Tufte cốt lõi
| # | Nguyên tắc | Giải thích | Matplotlib Action |
|---|---|---|---|
| 1 | Maximize Data-Ink Ratio | Loại bỏ mọi thứ không truyền tải data | Bỏ gridlines nặng, borders, background color |
| 2 | Erase Non-Data Ink | Xóa chart junk: 3D, shadows, gradients | ax.spines[...].set_visible(False) |
| 3 | Erase Redundant Data Ink | Không lặp lại thông tin | Không vừa data label vừa y-axis vừa legend cho cùng 1 data |
| 4 | Revise & Edit | Chart đầu tiên luôn cần chỉnh sửa | Iterate: draft → review → polish |
| 5 | Small Multiples | Nhiều chart nhỏ cùng scale tốt hơn 1 chart phức tạp | plt.subplots(2, 3) với shared axes |
Ví dụ — Before & After Tufte Declutter
python
import matplotlib.pyplot as plt
import numpy as np
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]
revenue = [32, 35, 38, 36, 42, 45]
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
# ❌ BEFORE — Chart Junk (low data-ink ratio)
ax1.bar(months, revenue, color="royalblue", edgecolor="gold",
linewidth=2)
ax1.set_facecolor("#F0F0F0") # Background xám — thừa
ax1.grid(True, color="white", linewidth=2) # Grid nặng — thừa
ax1.set_title("Monthly Revenue Report!!!", fontsize=16,
color="red", fontweight="bold") # Title quá lố
ax1.set_ylabel("Revenue (Billion VND)", fontsize=12)
ax1.legend(["Revenue"], loc="upper left", frameon=True,
shadow=True) # Legend thừa (chỉ 1 series)
# Giữ nguyên tất cả spines — border box nặng
# ✅ AFTER — Tufte Declutter (high data-ink ratio)
ax2.bar(months, revenue, color="#333333", width=0.6)
ax2.spines[["top", "right", "left"]].set_visible(False) # Bỏ borders thừa
ax2.tick_params(left=False) # Bỏ tick marks trái
ax2.set_yticks([]) # Bỏ y-axis (dùng data labels)
ax2.set_title("Revenue tăng đều — Q1-Q2/2025",
fontsize=13, fontweight="bold", loc="left") # Title = insight
# Data labels trực tiếp trên bar
for i, v in enumerate(revenue):
ax2.text(i, v + 0.5, f"{v}B", ha="center", fontsize=11, fontweight="bold")
fig.suptitle("Before vs After — Tufte Declutter", fontsize=15, y=1.02)
plt.tight_layout()
plt.savefig("tufte_before_after.png", dpi=150, bbox_inches="tight")
plt.show()Tufte's "Lie Factor"
Tufte cũng cảnh báo về Lie Factor — khi visual effect phóng đại hoặc thu nhỏ sự khác biệt thực tế:
- Lie Factor = 1.0 → trung thực
- Lie Factor > 1.05 → phóng đại (misleading)
- Lie Factor < 0.95 → thu nhỏ (misleading)
Ví dụ sai lầm phổ biến: truncated y-axis — cắt y-axis không bắt đầu từ 0 với bar chart → khác biệt 5% trông như 50%.
python
# ❌ Truncated y-axis — Lie Factor cao
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
brands = ["Brand A", "Brand B", "Brand C"]
share = [32, 34, 33]
# Misleading: y-axis từ 30 → 35
ax1.bar(brands, share, color=["#e74c3c", "#3498db", "#2ecc71"])
ax1.set_ylim(30, 36) # ← TRUNCATED! Brand B trông gấp 5x Brand A
ax1.set_title("❌ Misleading — Truncated Y-axis", fontsize=12)
ax1.set_ylabel("Market Share (%)")
# Honest: y-axis từ 0
ax2.bar(brands, share, color=["#e74c3c", "#3498db", "#2ecc71"])
ax2.set_ylim(0, 40) # ← HONEST! Gần bằng nhau — đúng thực tế
ax2.set_title("✅ Honest — Full Y-axis", fontsize=12)
ax2.set_ylabel("Market Share (%)")
plt.tight_layout()
plt.show()⚠️ Truncated y-axis
Bar chart phải bắt đầu y-axis từ 0 — vì chiều cao cột biểu thị magnitude. Nếu cắt y-axis, sai biệt 2% trông như gấp đôi. Line chart có thể truncate (vì thể hiện trend, không phải magnitude), nhưng cần ghi chú rõ.
Checklist Tufte's Principles
- [ ] Data-Ink Ratio > 0.8 — mỗi element trên chart đều truyền tải data
- [ ] Không 3D, shadow, gradient — chart flat 2D
- [ ] Bỏ grid nặng — nếu cần grid, dùng light dotted lines
- [ ] Bỏ spines thừa — chỉ giữ bottom (và left nếu cần)
- [ ] Data labels trực tiếp — giảm phụ thuộc vào legend
- [ ] Title = insight — "Revenue tăng 12% Q2" thay vì "Revenue Chart"
- [ ] Không truncate y-axis cho bar chart
- [ ] Small multiples khi có nhiều categories — subplot thay vì 1 chart phức tạp
- [ ] Lie Factor ≈ 1.0 — visual không phóng đại hay thu nhỏ data
3️⃣ Accessibility Standards — Colorblind-Safe & Inclusive Design
Giới thiệu
Accessibility trong data visualization đảm bảo rằng mọi người — bao gồm ~8% nam giới và ~0.5% nữ giới mắc chứng mù màu (color vision deficiency / CVD) — đều đọc được biểu đồ. Tiêu chuẩn WCAG 2.1 (Web Content Accessibility Guidelines) từ W3C là nền tảng, được bổ sung bởi best practices từ cộng đồng data visualization (Chartability, Visa Chart Components, US Web Design Standards).
Theo thống kê, trong phòng họp 25 người, trung bình 1-2 người không phân biệt được red-green — palette phổ biến nhất trong business charts. Nếu chart dùng "đỏ = xấu, xanh = tốt" mà không có indicator khác (pattern, label), 1-2 stakeholder sẽ không hiểu chart.
Áp dụng trong buổi học
3 nguyên tắc Accessibility cho chart
| # | Nguyên tắc | Giải thích | Matplotlib Implementation |
|---|---|---|---|
| 1 | Không dùng color alone | Dùng color + pattern/shape/label | hatch, marker, data labels |
| 2 | Colorblind-safe palette | Tránh red-green; dùng blue-orange | Palette: tab10, colorblind (Seaborn) |
| 3 | Contrast ratio ≥ 4.5:1 | Text & data đủ tương phản với background | Dùng dark colors trên white background |
Ví dụ — Colorblind-Safe Chart
python
import matplotlib.pyplot as plt
import seaborn as sns
# Colorblind-safe palette — IBM Design / Wong (2011)
cb_palette = ["#648FFF", # Blue
"#DC267F", # Magenta
"#FE6100", # Orange
"#FFB000", # Gold
"#785EF0"] # Violet
# ❌ Red-Green palette (không accessible)
bad_palette = ["#FF0000", "#00FF00", "#FF0000", "#00FF00", "#FF0000"]
# ✅ Accessible: color + pattern + label
categories = ["Engineering", "Marketing", "Sales", "HR", "Finance"]
values = [520, 300, 375, 150, 155]
hatches = ["", "//", "\\\\", "xx", ".."] # Patterns khác nhau
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
# ❌ Not accessible — red-green only
ax1.bar(categories, values, color=["red", "green", "red", "green", "red"])
ax1.set_title("❌ Red-Green Only — Not Accessible", fontsize=12)
ax1.set_ylabel("Headcount")
ax1.tick_params(axis="x", rotation=45)
# ✅ Accessible — colorblind-safe + hatches + labels
bars = ax2.bar(categories, values, color=cb_palette, edgecolor="black",
linewidth=0.8)
for bar, hatch in zip(bars, hatches):
bar.set_hatch(hatch)
# Data labels
for bar, val in zip(bars, values):
ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 8,
str(val), ha="center", fontsize=11, fontweight="bold")
ax2.set_title("✅ Colorblind-Safe + Patterns + Labels", fontsize=12)
ax2.set_ylabel("Headcount")
ax2.tick_params(axis="x", rotation=45)
ax2.spines[["top", "right"]].set_visible(False)
plt.tight_layout()
plt.savefig("accessibility_comparison.png", dpi=150, bbox_inches="tight")
plt.show()Seaborn Colorblind Palette
python
import seaborn as sns
# Seaborn built-in colorblind palette
sns.set_palette("colorblind")
# Hoặc set globally
plt.rcParams["axes.prop_cycle"] = plt.cycler(
color=sns.color_palette("colorblind")
)
# Từ giờ tất cả chart tự động dùng colorblind-safe colors
sns.barplot(data=df, x="department", y="salary", palette="colorblind")
plt.title("Salary by Department — Colorblind-Safe Palette")
plt.show()Alt-Text cho Chart
Khi export chart vào report (PDF, HTML, slide), luôn thêm alt-text — mô tả chart bằng text cho screen reader:
python
# Thêm alt-text vào figure metadata
fig, ax = plt.subplots()
ax.bar(categories, values, color=cb_palette)
ax.set_title("Headcount by Department")
# Alt-text (dùng khi embed vào HTML/report)
alt_text = ("Bar chart showing headcount by department. "
"Engineering leads with 520 employees, followed by "
"Sales (375), Marketing (300), Finance (155), and HR (150).")
# Matplotlib metadata
fig.text(0, -0.05, f"Alt: {alt_text}", fontsize=7, color="gray",
style="italic", transform=fig.transFigure)
plt.tight_layout()
plt.show()💡 Test accessibility dễ
Cách test nhanh chart có accessible không:
- In trắng đen (grayscale) — nếu vẫn phân biệt được các series → OK
- Bỏ legend — nếu vẫn hiểu chart nhờ data labels → OK
- Coblis simulator (coblis.com) — upload chart PNG để xem dưới mắt người colorblind
Checklist Accessibility Standards
- [ ] Không dùng color alone — mỗi series có thêm pattern, marker, hoặc label
- [ ] Palette colorblind-safe — dùng
sns.color_palette("colorblind")hoặc Wong palette - [ ] Không red-green — thay bằng blue-orange hoặc blue-red (xanh dương-đỏ OK vì ít người mù blue)
- [ ] Contrast ≥ 4.5:1 — text đủ đậm trên background
- [ ] Font size ≥ 10pt cho labels, ≥ 12pt cho title
- [ ] Alt-text — mô tả chart bằng text khi embed vào report
- [ ] Test grayscale — print chart trắng đen, vẫn đọc được
- [ ] Legible axis labels — không chồng chéo, rotation nếu cần
📊 Bảng tổng hợp
| Tiêu chuẩn | Phạm vi | Áp dụng Buổi 10 | Mức độ bắt buộc |
|---|---|---|---|
| IBCS | Business reporting — notation thống nhất | Actual/Plan/Forecast notation, variance chart, consistency | ⭐⭐⭐ Nên tuân thủ — tiêu chuẩn enterprise hàng đầu |
| Tufte's Principles | Design — data-ink ratio, declutter | Loại chart junk, data labels, honest axes, small multiples | ⭐⭐⭐ Gần như bắt buộc — nền tảng mọi visualization tốt |
| Accessibility | Inclusive design — colorblind-safe | Palette safe, pattern + label, alt-text, contrast | ⭐⭐ Nên tuân thủ — đặc biệt khi chart cho external audience |
⚠️ Quan trọng
3 tiêu chuẩn này không mâu thuẫn — chúng bổ sung nhau. IBCS cho bạn notation (cái gì = màu gì). Tufte cho bạn nguyên tắc thiết kế (bỏ cái gì). Accessibility cho bạn tính bao trùm (ai cũng đọc được). Chart chuyên nghiệp nhất là chart tuân thủ cả 3: consistent notation (IBCS) + clean design (Tufte) + accessible palette (WCAG).
📚 Tài liệu tham khảo
| Tài liệu | Link | Ghi chú |
|---|---|---|
| IBCS Standards (Official) | ibcs.com/standards | Bộ tiêu chuẩn SUCCESS đầy đủ |
| The Visual Display of Quantitative Information — Edward Tufte (1983) | edwardtufte.com | Sách gốc data-ink ratio |
| WCAG 2.1 — W3C | w3.org/TR/WCAG21 | Web Content Accessibility Guidelines |
| Wong (2011) — Points of View: Color Blindness | Nature Methods | Colorblind-safe palette gốc |
| Chartability | chartability.fizz.studio | Framework test accessibility cho chart |
| Seaborn Color Palettes | seaborn.pydata.org/tutorial/color_palettes.html | Hướng dẫn chọn palette |
| Coblis — Color Blindness Simulator | color-blindness.com/coblis | Upload chart → xem dưới mắt colorblind |