Khóa học
Giới thiệu
Theo Wikipedia, histogram là biểu diễn đồ họa chính xác về phân phối của dữ liệu số. Đây là ước lượng của phân phối xác suất của một biến liên tục (biến định lượng) và lần đầu tiên được giới thiệu bởi Karl Pearson. Nó là một dạng biểu đồ cột. Để xây dựng histogram, bước đầu tiên là “chia thùng” (bin) phạm vi giá trị — tức là chia toàn bộ phạm vi giá trị thành một loạt các khoảng — rồi đếm xem có bao nhiêu giá trị rơi vào mỗi khoảng. Các bins thường được xác định là các khoảng liên tiếp, không chồng lấp của một biến. Các bin (khoảng) phải kề nhau và thường (nhưng không bắt buộc) có kích thước bằng nhau.
Ngoài dữ liệu số, histogram cũng có thể dùng để trực quan hóa phân phối của ảnh, vì ảnh thực chất là tổ hợp các phần tử ảnh (pixel) trong khoảng từ $0$ đến $255$. Trục x của histogram biểu thị số lượng bin, còn trục y biểu diễn tần suất của một bin cụ thể. Số lượng bin là một tham số có thể thay đổi tùy vào cách bạn muốn trực quan hóa phân phối dữ liệu.
Ví dụ: Giả sử bạn muốn vẽ histogram của một ảnh RGB có giá trị pixel từ $0$ đến $255$. Số bin tối đa mà ảnh này có thể có là $255$. Nếu bạn giữ trục x (số lượng bin) là $255$, thì trục y sẽ biểu diễn tần suất của mỗi pixel trong ảnh đó.
Histogram về bản chất tương tự biểu đồ bar; hãy xem một ví dụ minh họa của histogram:

Histogram là công cụ tuyệt vời để trực quan hóa và hiểu phân phối xác suất của dữ liệu số hoặc dữ liệu ảnh theo cách hầu như ai cũng trực giác hiểu được. Python có rất nhiều lựa chọn để xây và vẽ histogram. Python có một vài thư viện dựng sẵn để tạo biểu đồ, và một trong số đó là matplotlib.
Trong hướng dẫn hôm nay, bạn sẽ chủ yếu dùng matplotlib để tạo và trực quan hóa histogram trên nhiều loại tập dữ liệu khác nhau.
Vậy thì không chần chừ nữa, hãy bắt đầu.
Vẽ Histogram bằng NumPy và Matplotlib
import numpy as np
Để tái lập kết quả, bạn sẽ dùng hàm seed của numPy, giúp cho mỗi lần chạy sẽ cho ra cùng một kết quả.
Bạn sẽ vẽ histogram của phân phối Gauss (chuẩn) có trung bình $0$ và độ lệch chuẩn $1$.
np.random.seed(100)
np_hist = np.random.normal(loc=0, scale=1, size=1000)
np_hist[:10]
array([-1.74976547, 0.3426804 , 1.1530358 , -0.25243604, 0.98132079,
0.51421884, 0.22117967, -1.07004333, -0.18949583, 0.25500144])
Hãy kiểm tra trung bình và độ lệch chuẩn của phân phối trên.
np_hist.mean(),np_hist.std()
(-0.016772157343909157, 1.0458427194167)
Tiếp theo, bạn sẽ dùng hàm histogram của numPy, hàm này trả về hist và bin_edges.
hist,bin_edges = np.histogram(np_hist)
hist
array([ 7, 37, 111, 207, 275, 213, 105, 31, 10, 4])
bin_edges
array([-3.20995538, -2.50316588, -1.79637637, -1.08958687, -0.38279736,
0.32399215, 1.03078165, 1.73757116, 2.44436066, 3.15115017,
3.85793967])
Tiếp theo, hãy vẽ histogram bằng hàm plt.bar của matplotlib, trong đó trục x và trục y của bạn lần lượt là bin_edges và hist.
import matplotlib.pyplot as plt
%matplotlib inline
plt.figure(figsize=[10,8])
plt.bar(bin_edges[:-1], hist, width = 0.5, color='#0504aa',alpha=0.7)
plt.xlim(min(bin_edges), max(bin_edges))
plt.grid(axis='y', alpha=0.75)
plt.xlabel('Value',fontsize=15)
plt.ylabel('Frequency',fontsize=15)
plt.xticks(fontsize=15)
plt.yticks(fontsize=15)
plt.ylabel('Frequency',fontsize=15)
plt.title('Normal Distribution Histogram',fontsize=15)
plt.show()

Hãy làm tròn bin_edges về số nguyên.
bin_edges = np.round(bin_edges,0)
plt.figure(figsize=[10,8])
plt.bar(bin_edges[:-1], hist, width = 0.5, color='#0504aa',alpha=0.7)
plt.xlim(min(bin_edges), max(bin_edges))
plt.grid(axis='y', alpha=0.75)
plt.xlabel('Value',fontsize=15)
plt.ylabel('Frequency',fontsize=15)
plt.xticks(fontsize=15)
plt.yticks(fontsize=15)
plt.ylabel('Frequency',fontsize=15)
plt.title('Normal Distribution Histogram',fontsize=15)
plt.show()

Bây giờ đồ thị trông đẹp hơn nhiều, phải không?
Để hiểu hist và bin_edges, hãy xem một ví dụ:
Bạn sẽ định nghĩa một mảng với các giá trị tùy ý và đặt bin với phạm vi $5$.
hist, bin_edges = np.histogram([1, 1, 2, 2, 2, 2, 3],bins=range(5))
hist
array([0, 2, 4, 1])
Trong kết quả trên, hist cho biết có $0$ phần tử ở bin $0$, $2$ ở bin $1$, $4$ ở bin $3$, $1$ ở bin $4$.
bin_edges
array([0, 1, 2, 3, 4])
Tương tự, đầu ra bin_edges ở trên có nghĩa là bin $0$ thuộc khoảng $[0,1)$, bin $1$ thuộc khoảng $[1,2)$, bin $2$ thuộc khoảng $[2,3)$, bin $3$ thuộc khoảng $[3,4)$.
Vẽ Histogram chỉ với Matplotlib
Vẽ histogram bằng matplotlib rất đơn giản. Bạn chỉ cần dùng hàm plt.hist() của matplotlib và truyền vào dữ liệu cùng số lượng bin và một vài tham số tùy chọn.
Trong plt.hist(), truyền bins='auto' sẽ cho bạn số lượng bin “lý tưởng”. Ý tưởng là chọn độ rộng bin tạo ra biểu diễn trung thực nhất cho dữ liệu của bạn.
Vậy là xong. Giờ hãy vẽ histogram của normal_distribution bạn đã tạo bằng numpy.
plt.figure(figsize=[10,8])
n, bins, patches = plt.hist(x=np_hist, bins=8, color='#0504aa',alpha=0.7, rwidth=0.85)
plt.grid(axis='y', alpha=0.75)
plt.xlabel('Value',fontsize=15)
plt.ylabel('Frequency',fontsize=15)
plt.xticks(fontsize=15)
plt.yticks(fontsize=15)
plt.ylabel('Frequency',fontsize=15)
plt.title('Normal Distribution Histogram',fontsize=15)
plt.show()

Vẽ hai histogram cùng lúc
plt.figure(figsize=[10,8])
x = 0.3*np.random.randn(1000)
y = 0.3*np.random.randn(1000)
n, bins, patches = plt.hist([x, y])

Vẽ Histogram của dữ liệu Iris bằng Pandas
Bạn sẽ dùng sklearn để tải một tập dữ liệu gọi là iris. Trong sklearn, có một thư viện tên datasets chứa tập dữ liệu Iris có thể tải ngay lập tức. Vậy hãy nhanh chóng tải tập dữ liệu iris.
from sklearn.datasets import load_iris
import pandas as pd
data = load_iris().data
names = ['sepal-length', 'sepal-width', 'petal-length', 'petal-width']
Tiếp theo, bạn sẽ tạo dataframe từ tập dữ liệu iris.
dataset = pd.DataFrame(data,columns=names)
dataset.head()
| sepal-length | sepal-width | petal-length | petal-width | |
|---|---|---|---|---|
| 0 | 5.1 | 3.5 | 1.4 | 0.2 |
| 1 | 4.9 | 3.0 | 1.4 | 0.2 |
| 2 | 4.7 | 3.2 | 1.3 | 0.2 |
| 3 | 4.6 | 3.1 | 1.5 | 0.2 |
| 4 | 5.0 | 3.6 | 1.4 | 0.2 |
dataset.columns[0]
'sepal-length'
Cuối cùng, bạn sẽ dùng hàm dựng sẵn .hist() của pandas để vẽ histogram cho tất cả các đặc trưng có trong tập dữ liệu.
Thật tuyệt vời phải không?
fig = plt.figure(figsize = (8,8))
ax = fig.gca()
dataset.hist(ax=ax)
plt.show()

Các biến petal-length, petal-width và sepal-length cho thấy phân phối đơn đỉnh, trong khi sepal-width phản ánh đường cong Gauss. Tất cả những điều này là các phân tích hữu ích vì từ đó bạn có thể cân nhắc dùng thuật toán hoạt động tốt với kiểu phân phối này.
Vẽ Histogram bằng Matplotlib
plt.figure(figsize=[10,10])
f,a = plt.subplots(2,2)
a = a.ravel()
for idx,ax in enumerate(a):
ax.hist(dataset.iloc[:,idx], bins='auto', color='#0504aa',alpha=0.7, rwidth=0.85)
ax.set_title(dataset.columns[idx])
plt.tight_layout()
<Figure size 720x720 with 0 Axes>

Vẽ Histogram của ảnh nhị phân và ảnh RGB
Ở phần cuối của hướng dẫn, bạn sẽ trực quan hóa hai loại ảnh khác miền: ảnh nhị phân (tài liệu) và ảnh tự nhiên. Ảnh nhị phân là những ảnh có giá trị pixel chủ yếu là $0$ hoặc $255$, trong khi ảnh kênh màu có giá trị pixel trong khoảng từ $0$ đến $255$.
Phân tích phân phối pixel bằng cách vẽ histogram các giá trị cường độ của ảnh là cách đúng đắn để đo tần suất xuất hiện của mỗi pixel với một ảnh cho trước. Với ảnh thang xám 8-bit, có 256 giá trị cường độ khả dĩ. Do đó, để nghiên cứu sự khác biệt giữa ảnh tài liệu và ảnh tự nhiên trong miền phân phối pixel, bạn sẽ vẽ hai histogram lần lượt cho ảnh tài liệu và ảnh tự nhiên. Xét rằng các tài liệu dễ đọc có tính thưa, phân phối pixel trong các ảnh tài liệu này nhìn chung sẽ bị lệch.
Bạn sẽ dùng mô-đun opencv để tải hai ảnh, chuyển chúng sang thang xám bằng cách truyền tham số $0$ khi đọc, và cuối cùng đổi kích thước chúng về cùng kích thước.
import cv2
lena_rgb = cv2.imread('lena.png')
lena_gray = cv2.cvtColor(lena_rgb,cv2.COLOR_BGR2GRAY)
lena_gray.shape
(512, 512)
binary = cv2.imread('binary.png')
binary.shape
(1394, 2190, 3)
binary = cv2.resize(binary,(512,512),cv2.INTER_CUBIC)
binary.shape
(512, 512, 3)
binary_gray = cv2.cvtColor(binary,cv2.COLOR_BGR2GRAY)
binary_gray.shape
(512, 512)
Hãy trực quan hóa cả ảnh tự nhiên và ảnh tài liệu.
plt.figure(figsize=[10,10])
plt.subplot(121)
plt.imshow(lena_rgb[:,:,::-1])
plt.title("Natural Image")
plt.subplot(122)
plt.imshow(binary[:,:,::-1])
plt.title("Document Image")
plt.show()

Lưu ý: Bạn sẽ dùng bins bằng $255$, vì có tổng cộng $255$ pixel trong một ảnh và bạn muốn trực quan hóa tần suất của mỗi giá trị pixel của ảnh trong khoảng từ $0$ đến $255$.
hist, edges = np.histogram(binary_gray,bins=range(255))
plt.figure(figsize=[10,8])
plt.bar(edges[:-1], hist, width = 0.8, color='#0504aa')
plt.xlim(min(edges), max(edges))
plt.grid(axis='y', alpha=0.75)
plt.xlabel('Value',fontsize=15)
plt.ylabel('Frequency',fontsize=15)
plt.xticks(fontsize=15)
plt.yticks(fontsize=15)
plt.ylabel('Frequency',fontsize=15)
plt.title('Document Image Histogram',fontsize=15)
plt.show()

hist, edges = np.histogram(lena_gray,bins=range(260))
plt.figure(figsize=[10,8])
plt.bar(edges[:-1], hist, width = 0.5, color='#0504aa')
plt.xlim(min(edges), max(edges))
plt.grid(axis='y', alpha=0.75)
plt.xlabel('Pixels',fontsize=15)
plt.ylabel('Frequency of Pixels',fontsize=15)
plt.xticks(fontsize=15)
plt.yticks(fontsize=15)
plt.ylabel('Frequency',fontsize=15)
plt.title('Natural Image Histogram',fontsize=15)
plt.show()

Từ các hình trên, bạn có thể quan sát rằng ảnh tự nhiên trải rộng trên toàn bộ 256 mức cường độ với phân phối ngẫu nhiên, trong khi ảnh tài liệu cho thấy phân phối đơn đỉnh và phần lớn bị lệch giữa các giá trị pixel từ $0$ đến $255$.
Kết luận
Chúc mừng bạn đã hoàn thành hướng dẫn.
Hướng dẫn này là điểm khởi đầu tốt để bạn tạo histogram bằng matplotlib với sự hỗ trợ của numpy và pandas.
Bạn cũng đã học cách tận dụng sức mạnh của histogram để phân biệt giữa hai miền ảnh khác nhau, cụ thể là ảnh tài liệu và ảnh tự nhiên.
Nếu bạn muốn tìm hiểu thêm về các kỹ thuật trực quan hóa dữ liệu, hãy cân nhắc khóa học Data Visualization with Matplotlib của DataCamp.
Vui lòng đặt câu hỏi liên quan đến hướng dẫn này ở phần bình luận bên dưới.