Tự Học Data Science · 18/09/2023 0

Chương 2 – Bài 1 – Hiểu về kiểu dữ liệu trong Python

Khi nghiên cứu Khoa học dữ liệu, sự hiệu quả và tốc độ tính toán sẽ phụ thuộc vào dữ liệu, do đó yêu cầu hiểu biết về cách dữ liệu được lưu trữ và xử lý. Phần này sẽ trình bày và so sánh cách các mảng dữ liệu được xử lý trong ngôn ngữ Python và cách NumPy cải thiện điều này. Hiểu sự khác biệt này là cơ bản để hiểu rõ nội dung trong phần còn lại của cuốn sách.

Người dùng Python thường được thu hút bởi sự dễ sử dụng của nó, một trong những yếu tố quan trọng là kiểu dữ liệu động. Trong khi một ngôn ngữ có kiểu tĩnh như C hoặc Java yêu cầu mỗi biến được khai báo rõ ràng, một ngôn ngữ có kiểu động như Python không cần quy định này. Ví dụ, trong C, bạn có thể chỉ định một phép toán cụ thể như sau:

Trong khi với Python, nó sẽ trông như thế này:

Lưu ý sự khác biệt chính: trong C, kiểu dữ liệu của mỗi biến được khai báo rõ ràng, trong khi trong Python các kiểu được linh hoạt, ta có thể gán bất kỳ loại dữ liệu nào cho bất kỳ biến nào.

Sự linh hoạt như vậy là một phần làm cho Python và các ngôn ngữ có kiểu động, thuận tiện và dễ sử dụng. Hiểu cách hoạt động này là một phần quan trọng giúp học cách phân tích dữ liệu một cách hiệu quả với Python. Nhưng điều này cũng chỉ ra là biến Python không chỉ là giá trị của chúng mà chúng còn chứa thông tin bổ sung về loại giá trị. Chúng ta sẽ khám phá điều này nhiều hơn trong các phần tiếp theo.

Một số nguyên Python không chỉ là một số nguyên

Phiên bản Python tiêu chuẩn được viết bằng ngôn ngữ C. Điều này có nghĩa là mỗi đối tượng Python chỉ đơn giản là một cấu trúc C được ẩn dưới dạng thông minh, chứa không chỉ giá trị của nó mà còn thông tin khác. Ví dụ, khi chúng ta định nghĩa một số nguyên trong Python, như x = 10000, x không chỉ đơn thuần là một số nguyên mà thực tế, nó là một con trỏ đến một đối tượng trong C phức tạp, chứa một số giá trị khác nhau.

Bạn sẽ không cần phải quá hiểu về những thành phần bên dưới

Nếu nhìn vào mã nguồn Python 3.4, chúng ta sẽ thấy rằng định nghĩa kiểu số nguyên (long) có dạng như sau:

Một số nguyên duy nhất trong Python 3.4 thực chất chứa bốn phần:

  • ob_refcnt, một số lượng tham chiếu giúp Python tự động xử lý việc cấp phát và giải phóng bộ nhớ
  • ob_type, mã hóa loại biến
  • ob_size, chỉ định kích thước của các thành viên dữ liệu liên quan
  • ob_digit, chứa giá trị số nguyên thực tế mà chúng ta mong đợi biến Python đại diện.

Điều này có nghĩa là có một số công việc phụ phí khi lưu trữ một số nguyên trong Python so với một số nguyên trong ngôn ngữ biên dịch như C, như minh họa trong hình sau:

Integer Memory Layout

Ở đây, PyObject_HEAD là phần của cấu trúc chứa đếm số tham chiếu, mã loại và các phần khác đã được đề cập trước đó.

Lưu ý sự khác biệt ở đây: một số nguyên trong C là một nhãn cho một vị trí trong bộ nhớ, trong đó các byte mã hoá một giá trị số nguyên. Một số nguyên trong Python là một con trỏ đến một vị trí trong bộ nhớ chứa tất cả thông tin về đối tượng Python, bao gồm cả các byte chứa giá trị số nguyên.Thông tin bổ sung này trong cấu trúc số nguyên Python là nguyên nhân cho việc Python có thể được viết mã một cách tự do và linh hoạt.Tất cả thông tin bổ sung này trong các loại dữ liệu của Python đều đi kèm với một chi phí, mà đặc biệt rõ ràng trong các cấu trúc kết hợp nhiều đối tượng này.

Một danh sách Python không chỉ là một danh sách

Hãy xem xét những gì xảy ra khi chúng ta sử dụng một cấu trúc dữ liệu Python chứa nhiều đối tượng Python.
Danh sách có thể sửa đổi và chứa nhiều phần tử là cấu trúc dữ liệu chuẩn trong Python. Chúng ta có thể tạo một danh sách số nguyên như sau:

Hoặc, tương tự, một danh sách các chuỗi:

Vì tính đa dạng của tính đa dạng của Python, chúng ta có thể tạo ra danh sách không đồng nhất:

L3 = [True, "2", 3.0, 4][type(item) for item in L3]
[bool, str, float, int]

Tuy nhiên, tính linh hoạt này có cái giá của nó: để cho phép những kiểu dữ liệu linh hoạt này, mỗi phần tử trong danh sách phải chứa thông tin về kiểu dữ liệu của nó, số lượng tham chiếu và thông tin khác – tức là, mỗi phần tử là một đối tượng Python hoàn chỉnh. Trong trường hợp đặc biệt mà tất cả các biến đều cùng kiểu dữ liệu, một phần lớn thông tin này là dư thừa: việc lưu trữ dữ liệu trong một mảng có kiểu dữ liệu cố định có thể hiệu quả hơn nhiều.Sự khác biệt giữa danh sách có kiểu dữ liệu động và mảng có kiểu dữ liệu cố định (như NumPy) được minh họa trong hình dưới đây:

Bố cục bộ nhớ của Mảng

Mảng bản chất là chứa một con trỏ duy nhất tới một khối dữ liệu liên tục. Trong khi đó, danh sách Python chứa một con trỏ tới một khối các con trỏ, mỗi con trỏ này lại trỏ tới một đối tượng Python như số nguyên Python chúng ta đã thấy trước đó. Một lần nữa, ưu điểm của list là tính linh hoạt, mảng kiểu cố định như NumPy thiếu tính linh hoạt này, nhưng lại hiệu quả hơn nhiều trong việc lưu trữ và xử lý dữ liệu.

Mảng có kiểu dữ liệu cố định trong Python

Python cung cấp một số lựa chọn khác nhau để lưu trữ dữ liệu trong bộ nhớ hiệu quả, với các bộ nhớ có kiểu dữ liệu cố định.Mô-đun tích hợp sẵn array (có sẵn từ Python 3.3) có thể được sử dụng để tạo các mảng cùng một kiểu:

import array
L = list(range(10))
A = array.array('i', L)A
array('i', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

Ở đây 'i' là tham số chỉ ra rằng thành phần của mảng là các số nguyên.

Nhưng mà nếu vậy thì đối tượng ndarray của NumPy hữu ích hơn nhiều.Trong khi đối tượng array của Python cung cấp việc lưu trữ hiệu quả dữ liệu dựa trên mảng, NumPy thêm vào đó các phép toán hiệu quả trên dữ liệu đó.Chúng ta sẽ khám phá những phép toán này trong các phần sau; ở đây chúng tôi sẽ thể hiện một số cách khác nhau để tạo một mảng NumPy.

Đó là những lý do quan trọng mà ta nên sử dụng numpy để xử lý các kiểu dữ liệu mảng, thay vì list.

Chúng ta sẽ bắt đầu với việc import NumPy theo chuẩn, dưới cái tên thay thế np:

import numpy as np

Tạo mảng từ danh sách Python

Đầu tiên, chúng ta có thể sử dụng np.array để tạo mảng từ danh sách Python:

Hãy nhớ rằng không giống list, NumPy bị ràng buộc với các mảng chứa cùng loại dữ liệu. Nếu các loại không khớp nhau, NumPy sẽ “tăng cường nếu có thể”nâng cấp” kiểu dữ liệu (ở đây, số nguyên được chuyển thành số thực – float):

np.array([3.14, 4, 2, 3])

Nếu chúng ta muốn rõ ràng thiết lập kiểu dữ liệu của mảng kết quả, chúng ta có thể sử dụng từ khóa dtype:

Một điểm cuối cùng, khác với danh sách Python, mảng NumPy có thể được khai báo rõ ràng là đa chiều; dưới đây là một cách khởi tạo một mảng đa chiều bằng cách sử dụng danh sách các danh sách:

# nested lists result in multi-dimensional arrays
np.array([range(i, i + 3) for i in [2, 4, 6]])
array([[2, 3, 4],
       [4, 5, 6],
       [6, 7, 8]])

Các list bên trong được xem như các hàng của mảng hai chiều .

Tạo một Mảng

Đặc biệt đối với các mảng lớn, việc tạo ra mảng từ đầu bằng cách sử dụng các hàm được tích hợp sẵn trong NumPy có hiệu suất cao hơn.Dưới đây là một số ví dụ:

Các Kiểu Dữ Liệu Chuẩn trong NumPy

Mảng NumPy chứa các giá trị của một loại duy nhất, vì vậy bạn phải có kiến thức chi tiết về các kiểu dữ liệu này và những hạn chế của chúng. Bởi vì NumPy được xây dựng bằng C, các kiểu dữ liệu này sẽ quen thuộc với người dùng C, Fortran và các ngôn ngữ liên quan khác.

Các kiểu dữ liệu NumPy tiêu chuẩn được liệt kê trong bảng dưới đây.Lưu ý rằng khi xây dựng một mảng, chúng có thể được chỉ định bằng cách sử dụng tham số dtype như đã nói ở trên:

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

Hoặc sử dụng đối tượng kiểu dữ liệu trong NumPy:

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

Để biết thêm thông tin, hãy tham khảo Tài liệu NumPy. NumPy cũng hỗ trợ các kiểu dữ liệu phức hợp, sẽ được giới thiệu trong Dữ liệu có cấu trúc: Mảng có cấu trúc của NumPy.