NUMPY
CÁC CHỦ ĐỀ
BÀI MỚI NHẤT
MỚI CẬP NHẬT

Thông báo: Download 4 khóa học Python từ cơ bản đến nâng cao tại đây.

Tạo mảng cơ bản với Numpy

Trong bài này ta sẽ đào sâu và tìm hiểu kỹ về cách tạo mảng cơ bản trong NumPy, cũng như sự hiệu quả của việc dùng NumPy so với List trong việc lưu trữ và thao tác với mảng.

test php

banquyen png
Bài viết này được đăng tại freetuts.net, không được copy dưới mọi hình thức.

Python là một ngôn ngữ động (dynamic language), do vậy việc khai báo biến trên Python cũng vô cùng đơn giản, ta xét đoạn code sau:

Trong C
int a = 2;
if (a % 2 == 0) {
  printf("%d la so chan", a);
}
Python
a = 2
if a % 2 == 0:
    print(a, “la so chan”)

Vì C là ngôn ngữ tĩnh (static language) nên các biến phải khai báo rõ ràng. Bạn có thể thấy trước biến a cần có tiền tố int (để khai báo rằng biến a thuộc kiểu dữ liệu integer) trong khi Python thì không cần điều này.

Chẳng hạn:

Bài viết này được đăng tại [free tuts .net]

Python
b = 5
b = “nam”
C/C++
int b = 5;
b = “nam” // Lỗi

Việc này khiến cho việc sử dụng Python trở nên tiện dụng hơn nhiều so với C, dù phải đánh đổi về hiệu năng. Ta không thể làm việc nhanh chóng mà phải để ý đến việc khai báo biến này thuộc kiểu dữ liệu này, kiểu dữ liệu kia, chưa kể sẽ xuất hiện hàng tá lỗi nếu set dữ liệu không cùng kiểu.

Đó cũng là lý do mà Python trở thành ngôn ngữ phổ biến nhất cho Data Science - đơn giản và tiện dụng. Trong các phần tiếp, ta sẽ tìm hiểu sâu về cơ chế hình thành nên 1 biến trong Python, đây là một khía cạnh quan trọng mà nhiều người hay bỏ qua, hiểu được những vấn đề cốt lõi này sẽ giúp việc phân tích dữ liệu một cách hiệu quả hơn.

1. Một biến trong Python được hình thành như thế nào?

Python vốn dĩ được viết trên C, do vậy hiển nhiên tất cả các biến của Python mà ta khai báo sẽ được khai báo trên C, mà ở đây chính là kiểu cấu trúc (struct).

Khi ta khai báo một số nguyên trên Python, chẳng hạn x = 100, thì nó không phải là số nguyên “thuần”, mà nó bản chất là một con trỏ và trỏ đến một struct trong C. Nếu tìm trong mã nguồn của Python 3 (CPython), một biến số nguyên được định nghĩa như thế này:

struct _longobject {
  long ob_refcnt;
  PyTypeObject *ob_type;
  size_t ob_size;
  long ob_digit[1];
};

Dễ thấy một biến số nguyên của Python gồm 4 phần:

  • ob_refcnt: tham chiếu cho Python cấp phát và giải phóng bộ nhớ
  • ob_type: mã hoá loại kiểu dữ liệu
  • ob_size: chỉ định kích thước của các dữ liệu thành viên
  • ob_digit: đây chính là nơi lưu giá trị số nguyên mà ta khai báo ban đầu

Ảnh dưới mô tả cách mà số nguyên trong C và Python được lưu trong bộ nhớ:

1 png

* Ghi chú: PyObject_HEAD chính là nơi chứa tất cả các tham chiếu, kiểu dữ liệu, kích thước,... đã đề cập ở trên (ob_refcnt, ob_type,...)

Từ đó, ta có thể thấy rõ được sự khác biệt của việc khai báo dữ liệu số nguyên trên Python so với C:

  • Một số nguyên trong C cơ bản là một nhãn (label) cho một vị trị trong bộ nhớ mà các byte đã mã hoá giá trị số nguyên, điều này khiến cho một biến đã khai báo kiểu dữ liệu trong C thì không thể set với kiểu khác được.
  • Còn trong Python, một số nguyên là một con trỏ chỉ đến một vị trí trong bộ nhớ nơi chứa một object đã để cập ở trên. Do vậy, ta có thể set kiểu dữ liệu khác mà không lo bị lỗi.

2. Cơ chế của List trong Python và sự hạn chế

Sau khi đã hiểu về cấu trúc của một biến trong Python, ta sẽ nhắc qua về List trong Python để nói về sự hạn chế của nó.

Chúng ta có thể tạo một mảng số nguyên trên Python như sau:

In [1]:
A = list(range(5))
A
Out [1]:
[0, 1, 2, 3, 4]
In[2]:
type(A[0])

Out[2]:
int

Hoặc một mảng với nhiều kiểu dữ liệu:

In[3]:
A1 = [True, "Freetuts", 1, 2.5]
[type(i) for i in A1]

Out[3]:
[bool, str, int, float]

Việc khai báo một mảng với nhiều kiểu dữ liệu đem tới nhiều sự thuận lợi, tuy nhiên ta có thể thấy rõ một vấn đề sau: Nếu trong mảng đều có chung kiểu dữ liệu thì sẽ tồn tại rất nhiều thông tin thừa (tham chiếu, kiểu dữ liệu,... trong PyObject_HEAD).

Do đó, List không thực sự tốt nếu ta cần xử lý các mảng dữ liệu nếu tất cả cùng chung một kiểu dữ liệu (mà hầu hết khi xử lý dữ liệu trong Data Science, mỗi mảng sẽ chỉ có một kiểu dữ liệu duy nhất). Vì vậy, sẽ hiệu quả hơn nhiều nếu như ta cố định toàn bộ kiểu dữ liệu vào trong một mảng nếu mảng đó chung kiểu dữ liệu (fixed-type arrays). Dù phải đánh đổi sự tiện lợi nhưng nó sẽ giúp thao tác và lưu trữ hiệu quả hơn, và đó chính là cách mà NumPy làm việc.

3. Tạo mảng với Numpy

Tạo từ List

Chúng ta có thể dùng nhiều cách để tạo fixed-type arrays trong Python, chẳng hạn từ Python 3.3 đi kèm với thư viện array:

In[4]:
import array
A = list(range(10))
A1 = array.array('i', L)
A1
Out[4]:
array('i', [0, 1, 2, 3, 4])

Note: “i” chính là viết tắt cho việc mảng chứa kiểu dữ liệu integers

Đây là một thư viện khá hữu ích, tuy nhiên nó chỉ mới cung cấp khả năng lưu trữ. Với ndarray - một object cốt lõi của NumPy thì ngoài lưu trữ thì nó còn có khả năng thao tác với dữ liệu (ta sẽ nói ở các bài sau).

Có rất nhiều cách để tạo mảng với NumPy, đầu tiên ta sẽ import NumPy vào notebook:

In[5]:
import numpy as np

Đầu tiên, ta có thể tạo mảng từ List bằng cách dùng np.array:

In[6]:
np.array([1, 6, 9, 12])

Out[6]:
array([ 1, 6, 9, 12])

Vì mảng NumPy bắt buộc phải cùng kiểu dữ liệu, nên nếu khác thì nó sẽ cố ép sao cho toàn bộ mảng cùng kiểu, chẳng hạn như:

In[7]:
np.array([6.99, 1, 2, 3])

Out[7]:
array([6.99, 1. , 2. , 3. ])

Ta thấy toàn bộ kiểu dữ liệu đã chuyển sang số thực để đồng bộ. Ngoài ra ta có thể khai báo trước kiểu dữ liệu của mảng:

In[8]:
np.array([1, 2, 3, 4], dtype='float32')

Out[8]:
array([1., 2., 3., 4.], dtype=float32)

Quan trọng nhất, mảng NumPy có thể đa chiều, không giống như List chỉ có thể lưu trữ dữ liệu 1 chiều, ví dụ:

In[9]:
np.array([range(i, i + 5) for i in [1, 2, 3, 4]])

Out[9]:
array([[1, 2, 3, 4, 5],
[2, 3, 4, 5, 6],
[3, 4, 5, 6, 7],
[4, 5, 6, 7, 8]])

Tạo từ các hàm có sẵn

NumPy hỗ trợ rất nhiều hàm có sẵn để tạo mảng, tiện lợi hơn nhiều so với tạo từ List. Ta sẽ xem xét 1 số ví dụ:

Tạo mảng có 5 phần tử mà mọi giá trị đều bằng 0
In[10]:
# Tạo mảng có 5 phần tử mà mọi giá trị đều bằng 0
np.zeros(5, dtype=int)

Out[10]:
array([0, 0, 0, 0, 0])
Tạo mảng đa chiều kích thước 5x5 mà mọi giá trị đều = 1
In[11]:
# Tạo mảng đa chiều kích thước 5x5 mà mọi giá trị đều = 1
np.ones((5, 5), dtype=float)

Out[11]:
array([[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.]])
Tạo mảng đa chiều kích thước 3x3 mà mọi giá trị đều = 100
In[12]:
# Tạo mảng đa chiều kích thước 3x3 mà mọi giá trị đều = 100
np.full((3, 3), 100)

Out[12]:
array([[100, 100, 100],
[100, 100, 100],
[100, 100, 100]])

Ta sẽ đến với một số ví dụ nâng cao hơn, chẳng hạn:

Dùng np.arange để tạo mảng tuyến tính, bắt đầu bằng 0, kết thúc bằng 10, bước nhảy là 2:
In[13]:
np.arange(0, 10, 2)

Out[13]:
array([0, 2, 4, 6, 8])
Dùng np.linspace để tạo mảng tuyến tính với 5 phần tử và các phần tử có giá trị từ 0 đến 2:
In[14]:
np.linspace(0, 2, 5)

Out[14]:
array([0. , 0.5, 1. , 1.5, 2. ])
Tạo mảng 3x3 phân bố đều với các phần tử có giá trị ngẫu nhiên giữa 0 và 1:
In[15]:
np.random.random((3, 3))

Out[15]:
array([[0.74096074, 0.52767225, 0.48543925],
[0.10406574, 0.6248593 , 0.40501661],
[0.52078079, 0.82908192, 0.85961909]])
Tạo một mảng 3x3 với các phần tử ngẫu nhiên được phân phối chuẩn với giá trị trung bình là 0 và độ lệch chuẩn 1
In[16]:
np.random.normal(0, 1, (3, 3))

Out[16]:
array([[ 0.58069184, 0.00102128, 0.66747731],
[ 0.92574049, -0.24678111, 0.6781257 ],
[-0.60611321, -0.54344727, 0.67134354]])
Tạo một mảng 3x3 các số nguyên ngẫu nhiên trong khoảng [0, 10)
In[17]:
np.random.randint(0, 10, (3, 3))

Out[17]:
array([[6, 2, 4],
[9, 8, 8],
[9, 1, 5]])
Tạo ma trận đơn vị kích thước 3x3:
In[18]:
np.eye(3)

Out[18]:
array([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
Tạo mảng rỗng gồm 5 phần tử, giá trị của 5 phần tử này có thể là bất cứ giá trị nào đã lưu trên bộ nhớ trước đó:
In[19]:
np.empty(5)

Out[19]:
array([0. , 0.5, 1. , 1.5, 2. ])

4. Kiểu dữ liệu trong NumPy

Trong NumPy có nhiều kiểu dữ liệu khác nhau, trong bảng dưới là danh sách các kiểu dữ liệu hỗ trợ bởi NumPy (sẽ không lạ lắm với những người đã từng sử dụng C)

Các kiểu dữ liệu cơ bản của NumPy:

Kiểu dữ liệu Chú thích
bool_ Kiểu Boolean, giá trị True hoặc False
int_ Kiểu số nguyên mặc định (giống C long; thường là int64 hoặc int32
intc Giống hệt với int C (thường là int32 hoặc int64)
intp Số nguyên được sử dụng để lập chỉ mục (giống như C ssize_t; thông thường là int32 hoặc int64)
int8 Byte (–128 to 127)
int16 Số nguyên (–32768 đến 32767)
int32 Số nguyên (–2147483648 đến 2147483647)
int64 Số nguyên (–9223372036854775808 đến 9223372036854775807)
uint8 Số nguyên không dấu (0 đến 255)
uint16 Số nguyên không dấu (0 đến 65535)
uint32 Số nguyên không dấu (0 đến 4294967295)
uint64 Số nguyên không dấu (0 đến 18446744073709551615)
float_ Viết tắt cho float64
float16 Half-precision float: sign bit, 5 bits exponent, 10 bits mantissa
float32 Single-precision float: sign bit, 8 bits exponent, 23 bits mantissa
float64 Double-precision float: sign bit, 11 bits exponent, 52 bits mantissa
complex_ Viết tắt cho complex128
complex64 Số phức, được biểu diễn bởi 32 bit floats
complex128 Số phức, được biểu diễn bởi 64 bit floats

* Lưu ý: Ta có thể định dạng kiểu dữ liệu của mảng bằng 2 cách:

np.zeros(10, dtype='int16')

Hoặc

np.zeros(10, dtypenp.int16)

5. Tổng kết

Qua bài trên, ta đã tìm hiểu được cơ bản về NumPy, về cách tạo mảng, kiểu dữ liệu, cũng như hiểu về cách thức mà một biến trong Python được hình thành.

Đây là một bài rất quan trọng, các bạn nên thử trên notebook tất cả các kiểu tạo mảng trên để có thể nắm bắt được các phương thức mà NumPy hỗ trợ. Trong bài tiếp theo, ta sẽ cùng nhau khám phá các thao tác xử lý mảng với NumPy. Hẹn gặp các bạn ở bài tiếp theo nhé.

Cùng chuyên mục:

Hướng dẫn xây dựng Command-Line Interface (CLI) bằng Quo trong Python

Hướng dẫn xây dựng Command-Line Interface (CLI) bằng Quo trong Python

Hướng dẫn toàn diện về module datetime trong Python

Hướng dẫn toàn diện về module datetime trong Python

Cách truy cập và thiết lập biến môi trường trong Python

Cách truy cập và thiết lập biến môi trường trong Python

Lớp dữ liệu (Data Classes) trong Python với decorator @dataclass

Lớp dữ liệu (Data Classes) trong Python với decorator @dataclass

Từ khóa yield trong Python

Từ khóa yield trong Python

Sự khác biệt giữa sort() và sorted() trong Python

Sự khác biệt giữa sort() và sorted() trong Python

Sử dụng Poetry để quản lý dependencies trong Python

Sử dụng Poetry để quản lý dependencies trong Python

Định dạng chuỗi Strings trong Python

Định dạng chuỗi Strings trong Python

Một tác vụ phổ biến khi làm việc với danh sách trong Python

Một tác vụ phổ biến khi làm việc với danh sách trong Python

Làm việc với các biến môi trường trong Python

Làm việc với các biến môi trường trong Python

Sự khác biệt giữa set() và frozenset() trong Python

Sự khác biệt giữa set() và frozenset() trong Python

Sự khác biệt giữa iterator và iterable trong Python

Sự khác biệt giữa iterator và iterable trong Python

Cách làm việc với file tarball/tar trong Python

Cách làm việc với file tarball/tar trong Python

Chuyển đổi kiểu dữ liệu trong Python

Chuyển đổi kiểu dữ liệu trong Python

Sự khác biệt giữa toán tử == và is trong Python

Sự khác biệt giữa toán tử == và is trong Python

Làm việc với file ZIP trong Python

Làm việc với file ZIP trong Python

Cách sử dụng ThreadPoolExecutor trong Python

Cách sử dụng ThreadPoolExecutor trong Python

Sự khác biệt giữa byte objects và string trong Python

Sự khác biệt giữa byte objects và string trong Python

Xử lý độ chính xác các hàm floor, ceil, round, trunc, format  trong Python

Xử lý độ chính xác các hàm floor, ceil, round, trunc, format trong Python

Cách lặp qua nhiều list với hàm zip() trong Python

Cách lặp qua nhiều list với hàm zip() trong Python

Top