06/04/2021, 14:46

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:

In[2]:
print("a2 ndim: ", a2.ndim)
print("a2 shape: ", a2.shape)
print("a2 size: ", a2.size)
Out[2]:
a2 ndim:  2
a2 shape:  (3, 4)
a2 size:  12

bai3 1 gif

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):

In[3]
print("a2 dtype: ", a2.dtype)
Out[3]:
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")
Out[4]
itemsize: 4  bytes
nbytes: 240  bytes

bai3 2 gif

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

Out[5]
array([0, 9, 1, 1, 0])
In[6]
a1[3]
Out[6]
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

Out
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:

In[10]:
a3[0,0,0] = 0
a3
Out[10]:
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ụ:

In[11]
a3[0,0,1] = "Zaidap.com"
Out[11]
---------------------------------------------------------------------------
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'

bai3 3 gif

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:

In[12]:
a3[0,0,1] = 1.2345
a3
Out[12]
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]]])

bai3 4 gif

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

In[13]
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])

Out[13]
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:

In[14]
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])
Out[14]
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 đó:

In[15]
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])
Out[15]
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ụ:

In[16]
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)
Out[16]
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:

In[17]
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")
Out[17]
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:

In[18]
g = np.arange(1, 10).reshape((3, 3))
print("g:", g)
Out[18]
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:

In[20]
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))
Out[20]
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):

In[21]
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]))
Out[21]
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):

In[22]
a = np.array([1,2,3])
b = np.array([2,3,4])
print(np.dstack([a,b]))

print("shape:", np.dstack([a,b]).shape)
Out[22]
[[[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ự:

In[42]
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()

In[48]
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)) 
Out[48]
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:

In[24]
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))
Out[24]
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.

Trần Trung Dũng

15 chủ đề

2610 bài viết

0