Xử lý dữ liệu trên mảng cơ bản với Numpy - ài liệu học Numpy từ cơ bản đến nâng cao
Trong bài này mình sẽ trình bày cách xử lý dữ liệu trên mảng với các thao tác như truy cập vào mảng con, chia (split), biến đổi kích thước (reshape), nối (join) và sắp xếp (sorting) mảng. 1. Thuộc tính mảng NumPy Trước khi đi đến các phần xử lý mảng, ta sẽ tìm hiểu về các thuộc tính mảng NumPy ...
Trong bài này mình sẽ trình bày cách xử lý dữ liệu trên mảng với các thao tác như truy cập vào mảng con, chia (split), biến đổi kích thước (reshape), nối (join) và sắp xếp (sorting) mảng.
1. Thuộc tính mảng NumPy
Trước khi đi đến các phần xử lý mảng, ta sẽ tìm hiểu về các thuộc tính mảng NumPy trước. Chúng ta sẽ lần lượt tạo 3 mảng:
import numpy as np a1 = np.random.randint(10, size=5) # Mảng 1 chiều a2 = np.random.randint(10, size=(3, 4)) # Mảng 2 chiều a3 = np.random.randint(10, size=(3, 4, 5)) # Mảng 3 chiều
Mỗi mảng sẽ có 3 thuộc tính chính:
- ndim: số chiều của mảng
- shape: kích thước của từng chiều
- size: tổng kích thước của mảng (bằng số lượng phần tử trong mảng)
Chẳng hạn, ta có thể kiểm tra thuộc tính của mảng a2:
print("a2 ndim: ", a2.ndim) print("a2 shape: ", a2.shape) print("a2 size: ", a2.size)
a2 ndim: 2 a2 shape: (3, 4) a2 size: 12
Một thuộc tính khác mà ta cũng cần quan tâm đó là dtype, thuộc tính này biểu thị kiểu dữ liệu của mảng (ta đã nói về kiểu dữ liệu ở bài 02):
print("a2 dtype: ", a2.dtype)
a2 dtype: int32
Thuộc tính cuối cùng mình muốn đề cập là itemsize (hiển thị kích thước tính bằng byte của mỗi mảng phần tử con) và nbytes (hiển thị tổng kích thước tính bằng byte của mảng), đây là thuộc tính khá hữu dụng nếu chúng ta muốn quan sát mảng đang chiếm bộ nhớ như thế nào, chẳng hạn:
print("itemsize:", a3.itemsize, " bytes") print("nbytes:", a3.nbytes, " bytes")
itemsize: 4 bytes nbytes: 240 bytes
2. Array Indexing
Nếu như bạn đã quen thuộc với cách truy cập các phần tử của List trong Python, thì với NumPy cũng khá tương tự.
Ví dụ:
Lệnh: a1
array([0, 9, 1, 1, 0])
a1[3]
1
Với mảng đa chiều thì cũng tương tự, bạn chỉ cần thêm dấu phẩy để ngăn cách các vị trị tương ứng:
Lệnh: a3
array([[[4, 5, 7, 9, 0], [8, 7, 6, 6, 2], [3, 4, 8, 6, 2], [9, 3, 0, 7, 5]], [[8, 1, 1, 8, 8], [1, 4, 6, 4, 9], [2, 8, 3, 3, 4], [8, 1, 1, 4, 4]], [[5, 2, 5, 0, 7], [0, 4, 1, 5, 1], [4, 6, 0, 6, 6], [4, 6, 2, 6, 6]]])
Ta cũng có thể chỉnh sửa trực tiếp giá trị của phần tử qua index:
a3[0,0,0] = 0 a3
array([[[0, 5, 7, 9, 0], [8, 7, 6, 6, 2], [3, 4, 8, 6, 2], [9, 3, 0, 7, 5]], [[8, 1, 1, 8, 8], [1, 4, 6, 4, 9], [2, 8, 3, 3, 4], [8, 1, 1, 4, 4]], [[5, 2, 5, 0, 7], [0, 4, 1, 5, 1], [4, 6, 0, 6, 6], [4, 6, 2, 6, 6]]])
Lưu ý quan trọng là như ở bài trước đã đề cập, mảng NumPy phải có chung 1 kiểu dữ liệu có định, do vậy nếu bạn thay đổi một giá trị nào không cùng kiểu dữ liệu thì NumPy sẽ báo lỗi, ví dụ:
a3[0,0,1] = "Zaidap.com"
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-11-b9e27521c930> in <module> ----> 1 a3[0,0,1] = "Zaidap.com" ValueError: invalid literal for int() with base 10: 'Zaidap.com'
Hoặc nếu cùng kiểu dữ liệu số thì NumPy sẽ ép giá trị vừa đặt về chung kiểu dữ liệu:
a3[0,0,1] = 1.2345 a3
array([[[0, 1, 1, 5, 5], [6, 1, 6, 6, 3], [9, 2, 6, 6, 4], [4, 9, 8, 6, 6]], [[1, 2, 7, 6, 6], [5, 0, 4, 4, 4], [2, 3, 6, 2, 7], [2, 2, 1, 0, 6]], [[0, 4, 0, 8, 5], [1, 5, 0, 8, 1], [9, 7, 8, 0, 9], [0, 7, 6, 4, 9]]])
3. Array Slicing
Cũng gần giống như cách mà ta truy cập vào các phần tử trong mảng, NumPy hỗ trợ ta cách truy cập vào mảng con cắt ra từ mảng chính bằng cú pháp sau:
a[start:stop:step]
Nếu như có tham số nào trong 3 tham số trên không được điền, NumPy sẽ mặc định như sau:
- start = 0
- stop = kích thước của chiều
- step = 1
Chúng ta sẽ đi vào các ví dụ cụ thể:
Mảng con 1 chiều
x = np.arange(5) print("x = ", x) print("Lấy 3 phần tử đầu: ", x[:3]) print("Lấy 3 phần tử ở giữa: ", x[1:4]) print("Lấy các phần tử với bước nhảy = 2", x[::2]) print("Lấy các phần tử với bước nhảy = 2, bắt đầu tại vị trí = 1", x[1::2])
x = [0 1 2 3 4] Lấy 3 phần tử đầu: [0 1 2] Lấy 3 phần tử ở giữa: [1 2 3] Lấy các phần tử với bước nhảy = 2 [0 2 4] Lấy các phần tử với bước nhảy = 2, bắt đầu tại vị trí = 1 [1 3]
Mảng con nhiều chiều
Mảng con nhiều chiều về cơ bản cũng giống như mảng con 1 chiều, ta xem xét các ví dụ sau:
x2 = np.random.randint(10, size=(3, 4)) print("x2 = ", x2) print("Lấy 2 hàng và 3 cột: ", x2[:2, :3]) print("Lấy tất cả các hàng, còn lấy các cột với bước nhảy là 2: ", x2[:, ::2])
x2 = [[7 8 6 0] [1 5 5 1] [1 5 9 2]] Lấy 2 hàng và 3 cột: [[7 8 6] [1 5 5]] Lấy tất cả các hàng, còn lấy các cột với bước nhảy là 2: [[7 6] [1 5] [1 9]]
Truy xuất mảng cột và hàng
Có một số trường hợp ta chỉ cần lấy 1 cột hoặc một hàng, thì NumPy cũng hỗ trợ việc đó:
print("Lấy cột đầu tiên: ", x2[:, 0]) print("Lấy hàng đầu tiên", x2[0, :]) print("Tương tự như x2[0, :]", x2[0])
Lấy cột đầu tiên: [7 1 1] Lấy hàng đầu tiên [7 8 6 0] Tương tự như x2[0, :] [7 8 6 0]
Tạo bản copy với mảng
Một trong những tính năng quan trọng trong NumPy mà khác với List đó là nếu như ta sửa đổi giá trị trong mảng con vừa lấy ra thì nó sẽ thay đổi luôn mảng chính, ví dụ:
x2_con = x2[:2, :2] #Lấy mảng 2x2 từ x2 print("x2_con: ", x2_con) x2_con[1, 1] = 100 #Đặt giá trị = 100 tại vị trí [1, 1] print("x2_con: ", x2_con) # x2 đã thay đổi giá trị print("x2: ", x2)
x2_con: [[7 8] [1 5]] x2_con: [[ 7 8] [ 1 100]] x2: [[ 7 8 6 0] [ 1 100 5 1] [ 1 5 9 2]]
Như vậy ta có thể thấy nếu thay đổi giá trị trên mảng con, mảng chính sẽ thay đổi theo. Nếu muốn tránh việc này, ta chỉ cần thêm method copy() như sau:
x2_copy = x2[:2, :2].copy() #Thêm phương thức copy() print("x2_copy:", x2_copy) x2_copy[1, 1] = 9 #Thay đổi tại vị trí [1, 1] print("x2_copy: ", x2_copy) print("x2")
x2_copy: [[ 7 8] [ 1 100]] x2_copy: [[7 8] [1 9]] x2
4. Array Reshaping
Biến đổi kích thước (reshape) là một trong những tính năng quan trọng mà ta thường sử dụng khá nhiều. Ví dụ như ta muốn biến mảng 1x9 chứa dãy số từ 1 => 9 thành mảng 3x3:
g = np.arange(1, 10).reshape((3, 3)) print("g:", g)
g: [[1 2 3] [4 5 6] [7 8 9]]
Điều cần chú ý là kích thước reshape phải tương ứng với kích thước của mảng (như ví dụ trên, 1x9 = 3x3).
5. Array Concatenation & Splitting
a. Nối mảng (concatenation)
Có 4 phương thức chính hỗ trợ nối mảng trong NumPy là np.concatenate, np.vstack, np.hstack và np.dstack.
Ta sẽ xem xét các ví dụ về phương thức np.concatenate:
x = np.array([0, 1, 2, 3]) y = np.array([3, 2, 1, 0]) print("Phương thức concatenate nhận tham số đầu tiên là một tuple hoặc mảng: ", np.concatenate([x, y])) print("Phương thức concatenate có thể nối nhiều hơn 2 mảng một lúc: ", np.concatenate([x, y, x, y])) x_grid = np.array([[1,2,3], [4,5,6]]) print("Sử dụng cho mảng nhiều chiều, nối 2 mảng theo trục đầu tiên (chiều dọc): ", np.concatenate([x_grid, x_grid])) print("Hoặc nối 2 mảng theo trục thứ 2 (chiều ngang): ", np.concatenate([x_grid, x_grid], axis=1))
Phương thức concatenate nhận tham số đầu tiên là một tuple hoặc mảng: [0 1 2 3 3 2 1 0] Phương thức concatenate có thể nối nhiều hơn 2 mảng một lúc: [0 1 2 3 3 2 1 0 0 1 2 3 3 2 1 0] Sử dụng cho mảng nhiều chiều, nối 2 mảng theo trục đầu tiên (chiều dọc): [[1 2 3] [4 5 6] [1 2 3] [4 5 6]] Hoặc nối 2 mảng theo trục thứ 2 (chiều ngang): [[1 2 3 1 2 3] [4 5 6 4 5 6]]
Chúng ta hoàn toàn có thể nối mảng chỉ cần dùng np.concatenate, tuy nhiên khi nối các mảng mà số chiều khác nhau, chỉ sử dụng np.concatenate sẽ rất phức tạp, khi đó ta nên dùng np.vstack (nối theo chiều dọc) và np.hstack (nối theo chiều ngang):
x = np.array([1,2,3]) x_grid = np.array([[4,5,6], [7,8,9]]) print("Nối 2 mảng theo chiều dọc: ", np.vstack([x, x_grid])) y = np.array([[99], [99]]) print("Nối 2 mảng theo chiều ngang: ", np.hstack([x_grid, y]))
Nối 2 mảng theo chiều dọc: [[1 2 3] [4 5 6] [7 8 9]] Nối 2 mảng theo chiều ngang: [[ 4 5 6 99] [ 7 8 9 99]]
Với np.dstack, các mảng sẽ được nối với nhau theo trục thứ 3 (third axis):
a = np.array([1,2,3]) b = np.array([2,3,4]) print(np.dstack([a,b])) print("shape:", np.dstack([a,b]).shape)
[[[1 2] [2 3] [3 4]]] shape: (1, 3, 2)
b. Chia mảng (splitting)
Ngược lại với nối là chia mảng, và cũng giống y như nối mảng, chia mảng có 4 hàm chính: np.split, np.hsplit, np.vsplit và np.dsplit với tính năng tương tự:
x = [1,2,3,4,5,6,3,2,1] x1, x2, x3 = np.split(x, [3, 5]) print("x1, x2, x3 = ", x1, x2, x3) grid = np.arange(16).reshape((4, 4)) print("grid = ", grid) tren, duoi = np.vsplit(grid, [2]) print("Trên:", tren) print("Dưới:", duoi) trai, phai = np.hsplit(grid, [2]) print("Trái:", trai) print("Phải:", phai) x = np.arange(16).reshape(2, 2, 4) print("x = ", x) print("Chia với theo axis = 2: ") print(np.dsplit(x, 2))
x1, x2, x3 = [1 2 3] [4 5] [6 3 2 1] grid = [[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11] [12 13 14 15]] Trên: [[0 1 2 3] [4 5 6 7]] Dưới: [[ 8 9 10 11] [12 13 14 15]] Trái: [[ 0 1] [ 4 5] [ 8 9] [12 13]] Phải: [[ 2 3] [ 6 7] [10 11] [14 15]] x = [[[ 0 1 2 3] [ 4 5 6 7]] [[ 8 9 10 11] [12 13 14 15]]] Chia với theo axis = 2: [array([[[ 0, 1], [ 4, 5]], [[ 8, 9], [12, 13]]]), array([[[ 2, 3], [ 6, 7]], [[10, 11], [14, 15]]])]
6. Array Sorting
Sử dụng hàm sort()
arr = np.array([[9, 13], [5, 2]]) print ("Sort theo cột : ", np.sort(arr, axis = 0)) print (" Sort theo hàng : ", np.sort(arr, axis = 1)) print (" Sort không theo trục : ", np.sort(arr, axis=None))
Sort theo cột : [[ 5 2] [ 9 13]] Sort theo hàng : [[ 9 13] [ 2 5]] Sort không theo trục : [ 2 5 9 13]
Sử dụng argsort()
Phương thức argsort sẽ trả về các vị trí của các phần tử được sắp xếp so với mảng ban đầu:
arr = np.array([8, 2, 1, 7, 4, 3, 9]) print("arr = ", arr) print("Mảng đã sắp xếp: ", np.sort(arr)) print("Vị trí tương ứng của các phần tử đã sắp xếp so với ban đầu: ", np.argsort(arr))
arr = [8 2 1 7 4 3 9] Mảng đã sắp xếp: [1 2 3 4 7 8 9] Vị trí tương ứng của các phần tử đã sắp xếp so với ban đầu: [2 1 5 4 3 0 6]
7. Tổng kết
Sau khi hoàn thành bài này thì bạn đã cơ bản nắm được NumPy và hiểu được cơ chế nó hoạt động, với mỗi phần trong bài, hãy thử với nhiều ví dụ khác nhau để có thể hiểu rõ hơn nhé. Trong những bài sau, ta sẽ đi sâu vào lý do vì sao mà NumPy lại là nhân tố quan trọng nhất trong hệ sinh thái Data Science của Python. Hẹn gặp các bạn trong bài sau.