Tự Học Data Science · 13/10/2023 0

02.06 Comparisons, Masks, and Boolean Logic

Ví dụ: Đếm Ngày Mưa

Hãy tưởng tượng bạn có một loạt dữ liệu biểu thị lượng mưa hàng ngày trong một năm tại một thành phố cụ thể.Ví dụ, ở đây chúng ta sẽ tải thống kê mưa hàng ngày cho thành phố Seattle trong năm 2014, sử dụng Pandas (được trình bày chi tiết hơn trong Chương 3):

import numpy as npimport pandas as pd# use pandas to extract rainfall inches as a NumPy arrayrainfall = pd.read_csv('data/Seattle2014.csv')['PRCP'].valuesinches = rainfall / 254.0  # 1/10mm -> inchesinches.shape
(365,)

Mảng này chứa 365 giá trị, biểu thị lượng mưa hàng ngày tính bằng inch từ ngày 1 tháng 1 đến ngày 31 tháng 12 năm 2014.

Như một bước đầu tiên để hiển thị dữ liệu, chúng ta hãy xem biểu đồ phân phối tần suất ngày mưa sử dụng thư viện Matplotlib (chúng ta sẽ khám phá công cụ này chi tiết hơn trong Chương 4):

%matplotlib inlineimport matplotlib.pyplot as pltimport seaborn; seaborn.set()  # set plot styles
plt.hist(inches, 40);
ảnh ví dụ - data science lại blog của lưu

Đồ thị này cho chúng ta một ý tưởng chung về mức độ mưa được đo trong một ngày: mặc dù có danh tiếng, phần lớn các ngày tại Seattle chỉ có mức lượng mưa đo được gần như bằng không trong năm 2014.Tuy nhiên, điều này không thể truyền tải đủ thông tin mà chúng ta muốn xem: ví dụ, trong năm có bao nhiêu ngày mưa? Lượng mưa trung bình trong những ngày mưa đó là bao nhiêu? Có bao nhiêu ngày có lượng mưa vượt quá nửa inch?

Đào sâu vào dữ liệu

Một phương pháp để làm điều này là trả lời các câu hỏi bằng cách thực hiện bằng tay: lặp qua dữ liệu, tăng giá trị của một biến đếm mỗi khi chúng ta gặp giá trị trong một phạm vi mong muốn.Vì những lý do được thảo luận trong suốt chương này, một phương pháp như vậy rất không hiệu quả, cả về mặt thời gian viết mã và thời gian tính toán kết quả.Chúng ta đã thấy trong Điện toán trên Mảng NumPy: Các Hàm Chung rằng ufuncs của NumPy có thể được sử dụng thay thế cho vòng lặp để thực hiện các phép tính toán nhanh chóng trên mảng; theo cùng một cách, chúng ta có thể sử dụng các ufuncs khác để thực hiện các phép so sánh từng phần tử trên mảng, và sau đó chúng ta có thể điều chỉnh kết quả để trả lời các câu hỏi mà chúng ta có.Bây giờ, chúng ta sẽ tạm thời bỏ qua dữ liệu và thảo luận về một số công cụ chung trong NumPy để sử dụng masking để nhanh chóng trả lời các loại câu hỏi như thế này.

Toán tử so sánh trong nhưng hàm ufunc

Trong Tính toán trên Mảng NumPy: Các Hàm Universal chúng ta đã giới thiệu ufuncs và tập trung vào các toán tử số học. Chúng ta thấy rằng sử dụng +, -, *, / và các toán tử khác trên các mảng dẫn đến các phép toán từng phần tử.NumPy cũng triển khai các toán tử so sánh như < (nhỏ hơn) và > (lớn hơn) như là các ufuncs từng phần tử.Kết quả của các toán tử so sánh này luôn là một mảng với kiểu dữ liệu Boolean.Có sẵn tất cả sáu phép so sánh tiêu chuẩn:

x = np.array([1, 2, 3, 4, 5])
x < 3  # less than
array([ True,  True, False, False, False], dtype=bool)
x > 3  # greater than
array([False, False, False,  True,  True], dtype=bool)
x <= 3  # less than or equal
array([ True,  True,  True, False, False], dtype=bool)
x >= 3  # greater than or equal
array([False, False,  True,  True,  True], dtype=bool)
x != 3  # not equal
array([ True,  True, False,  True,  True], dtype=bool)
x == 3  # equal
array([False, False,  True, False, False], dtype=bool)

Cũng có thể thực hiện so sánh phần tử theo từng phần tử của hai mảng, và tính biểu thức phức hợp:

(2 * x) == (x ** 2)
array([False,  True, False, False, False], dtype=bool)

Như trong trường hợp của các toán tử số học, các toán tử so sánh được hiện thực dưới dạng ufuncs trong NumPy, ví dụ, khi bạn viết x < 3, NumPy nội bộ sẽ sử dụng np.less(x, 3).

Giống như trong trường hợp của các hàm ufuncs số học, chúng sẽ hoạt động trên các mảng có kích thước và hình dạng bất kỳ.Dưới đây là một ví dụ hai chiều:

rng = np.random.RandomState(0)x = rng.randint(10, size=(3, 4))x
array([[5, 0, 3, 3],       [7, 9, 3, 5],       [2, 4, 7, 6]])
x < 6
array([[ True,  True,  True,  True],       [False, False,  True,  True],       [ True,  True, False, False]], dtype=bool)

Trong mỗi trường hợp, kết quả là một mảng Boolean, và NumPy cung cấp một số mẫu đơn giản để làm việc với những kết quả này.

Làm việc với Mảng Boolean

Cho một mảng Boolean, có một số hoạt động hữu ích bạn có thể thực hiện.Chúng ta sẽ làm việc với x, mảng hai chiều chúng ta đã tạo trước đó.

print(x)
[[5 0 3 3] [7 9 3 5] [2 4 7 6]]

Đếm các bài viết

Để đếm số lượng phần tử True trong một mảng Boolean, np.count_nonzero rất hữu ích:

# how many values less than 6?np.count_nonzero(x < 6)
8

Chúng ta nhận thấy có tám mục trong mảng nhỏ hơn 6.Một cách khác để truy cập thông tin này là sử dụng np.sum; trong trường hợp này, False được hiểu là 0, và True được hiểu là 1:

np.sum(x < 6)
8

Lợi ích của sum() là như với các hàm tổng hợp khác trong NumPy, việc tổng hợp này cũng có thể được thực hiện theo hàng hoặc cột:

# how many values less than 6 in each row?np.sum(x < 6, axis=1)
array([4, 2, 2])

Đoạn mã HTML trên đếm số giá trị nhỏ hơn 6 trong mỗi hàng của ma trận.

Nếu chúng ta quan tâm đến việc kiểm tra nhanh chóng xem bất kỳ hoặc tất cả các giá trị có đúng hay không, chúng ta có thể sử dụng (như bạn đã đoán) np.any hoặc np.all:

# are there any values greater than 8?np.any(x > 8)
True
# are there any values less than zero?np.any(x < 0)
False
# are all values less than 10?np.all(x < 10)
True
# are all values equal to 6?np.all(x == 6)
False

np.allnp.any cũng có thể được sử dụng trên các trục cụ thể. Ví dụ:

# are all values in each row less than 8?np.all(x < 8, axis=1)
array([ True, False,  True], dtype=bool)

Ở đây tất cả các phần tử trong hàng đầu tiên và hàng thứ ba đều nhỏ hơn 8, trong khi không phải là trường hợp với hàng thứ hai.

Cuối cùng, một lưu ý nhanh: như đã đề cập trong Nhóm hợp: Min, Max và tất cả những thứ còn lại, Python có các hàm sẵn có sum(), any()all(). Chúng có cú pháp khác với phiên bản NumPy và đặc biệt sẽ gặp lỗi hoặc cho kết quả không mong muốn khi được sử dụng trên mảng đa chiều. Hãy chắc chắn rằng bạn đang sử dụng np.sum(), np.any()np.all() cho những ví dụ này!

Phép toán Boolean

Chúng ta đã thấy cách đếm, ví dụ như tất cả các ngày có lượng mưa nhỏ hơn bốn inches, hoặc tất cả các ngày có lượng mưa lớn hơn hai inches.Nhưng nếu chúng ta muốn biết về tất cả các ngày có lượng mưa nhỏ hơn bốn inches và lớn hơn một inch thì sao?Điều này được thực hiện thông qua các toán tử logic theo bit của Python, &, |, ^, và ~.Tương tự như các toán tử số học tiêu chuẩn, NumPy quá tải chúng như các ufunc hoạt động theo từng phần tử trên các mảng (thông thường là Boolean).

Ví dụ, chúng ta có thể giải quyết loại câu hỏi phức tạp như sau:

np.sum((inches > 0.5) & (inches < 1))
29

Chúng ta thấy rằng có 29 ngày có lượng mưa từ 0.5 đến 1.0 inches.

Lưu ý rằng các dấu ngoặc trong đây quan trọng – do quy tắc ưu tiên của toán tử, nếu loại bỏ các dấu ngoặc này, biểu thức sẽ được tính toán như sau, dẫn đến một lỗi:

inches > (0.5 & inches) < 1

Sử dụng tương đương của A AND BNOT (NOT A OR NOT B) (mà bạn có thể nhớ nếu đã học một khóa học logic cơ bản), chúng ta có thể tính toán kết quả tương tự một cách khác:

np.sum(~( (inches <= 0.5) | (inches >= 1) ))
29

Kết hợp toán tử so sánh và toán tử logic trên mảng có thể dẫn đến một loạt các phép toán logic hiệu quả.

Cách thức hoạt động của các toán tử logic bitwise và các hàm ufunc tương đương được tóm tắt trong bảng sau:

Sử dụng các công cụ này, chúng ta có thể bắt đầu trả lời những loại câu hỏi mà chúng ta có về dữ liệu thời tiết của chúng ta.Dưới đây là một số ví dụ về các kết quả mà chúng ta có thể tính toán khi kết hợp giám sát với tổng hợp:

print("Number days without rain:      ", np.sum(inches == 0))print("Number days with rain:         ", np.sum(inches != 0))print("Days with more than 0.5 inches:", np.sum(inches > 0.5))print("Rainy days with < 0.2 inches  :", np.sum((inches > 0) &                                                (inches < 0.2)))
Number days without rain:       215Number days with rain:          150Days with more than 0.5 inches: 37Rainy days with < 0.2 inches  : 75

Mảng Boolean như là các Mặt nạ

Trong phần trước chúng ta đã xem xét về các tổng hợp được tính trực tiếp trên mảng các giá trị Boolean.Một mẫu mạnh mẽ hơn là sử dụng mảng Boolean như một mặt nạ (mask), để chọn các phần con cụ thể của dữ liệu.Quay trở lại mảng x từ trước đó, giả sử chúng ta muốn một mảng gồm tất cả các giá trị trong mảng mà nhỏ hơn, ví dụ, 5:

x
array([[5, 0, 3, 3],       [7, 9, 3, 5],       [2, 4, 7, 6]])

Chúng ta có thể dễ dàng lấy được một mảng Boolean cho điều kiện này, như chúng ta đã thấy trước đó:

x < 5
array([[False,  True,  True,  True],       [False, False,  True, False],       [ True,  True, False, False]], dtype=bool)

Bây giờ để chọn những giá trị từ mảng này, chúng ta có thể đơn giản là sử dụng chỉ số trên mảng Boolean này; điều này được gọi là một thao tác masking:

x[x < 5]
array([0, 3, 3, 3, 2, 4])

Điều được trả về là một mảng một chiều được điền đầy với tất cả các giá trị thỏa mãn điều kiện này, có nghĩa là tất cả các giá trị ở các vị trí mà mảng mask là True.

Chúng ta sau đó tự do thao tác trên các giá trị này theo ý muốn.Ví dụ, chúng ta có thể tính toán một số số liệu thống kê có liên quan trên dữ liệu mưa ở Seattle của chúng ta:

# construct a mask of all rainy daysrainy = (inches > 0)# construct a mask of all summer days (June 21st is the 172nd day)days = np.arange(365)summer = (days > 172) & (days < 262)print("Median precip on rainy days in 2014 (inches):   ",      np.median(inches[rainy]))print("Median precip on summer days in 2014 (inches):  ",      np.median(inches[summer]))print("Maximum precip on summer days in 2014 (inches): ",      np.max(inches[summer]))print("Median precip on non-summer rainy days (inches):",      np.median(inches[rainy & ~summer]))
Median precip on rainy days in 2014 (inches):    0.194881889764Median precip on summer days in 2014 (inches):   0.0Maximum precip on summer days in 2014 (inches):  0.850393700787Median precip on non-summer rainy days (inches): 0.200787401575

Bằng cách kết hợp các hoạt động Boolean, các hoạt động lẫn khuỷu tụ, chúng ta có thể nhanh chóng trả lời các câu hỏi này cho tập dữ liệu của chúng ta.

Một điểm chung gây nhầm lẫn là sự khác biệt giữa các từ khóa andor ở một mặt, và các toán tử &| ở mặt khác.Khi nào bạn sử dụng một cái trong số đó?

Sự khác biệt là như sau: andor đánh giá sự thật hoặc sai của toàn bộ đối tượng, trong khi &| đề cập đến các bit bên trong mỗi đối tượng.

Khi sử dụng and hoặc or, nghĩa là bạn yêu cầu Python coi đối tượng đó như một thực thể Boolean duy nhất.Trong Python, tất cả các số nguyên không bằng không đều đánh giá là True. Do đó:

bool(42), bool(0)
(True, False)
bool(42 and 0)
False
bool(42 or 0)
True

Khi bạn sử dụng &| trên số nguyên, biểu thức áp dụng lên các bit của phần tử, thực hiện phép and hoặc phép or trên các bit riêng lẻ của số:

bin(42)
'0b101010'
bin(59)
'0b111011'
bin(42 & 59)
'0b101010'
bin(42 | 59)
'0b111011'

Lưu ý rằng các bit tương ứng trong biểu diễn nhị phân được so sánh theo thứ tự để thu được kết quả.

Khi bạn có một mảng các giá trị Boolean trong NumPy, điều này có thể được coi như một chuỗi các bit trong đó 1 = True0 = False, và kết quả của &| hoạt động tương tự như trên:

A = np.array([1, 0, 1, 0, 1, 0], dtype=bool)B = np.array([1, 1, 1, 0, 1, 1], dtype=bool)A | B
array([ True,  True,  True, False,  True,  True], dtype=bool)

Sử dụng or trên các mảng này sẽ cố gắng đánh giá tính đúng hay sai của toàn bộ đối tượng mảng, điều này không phải là một giá trị được xác định rõ:

A or B
---------------------------------------------------------------------------ValueError                                Traceback (most recent call last)<ipython-input-38-5d8e4f2e21c0> in <module>()----> 1 A or BValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

Tương tự, khi thực hiện biểu thức Boolean trên một mảng cho trước, bạn nên sử dụng | hoặc & thay vì or hoặc and:

x = np.arange(10)(x > 4) & (x < 8)
array([False, False, False, False, False,  True,  True,  True, False, False], dtype=bool)

Cố gắng đánh giá đúng hay sai của toàn bộ mảng sẽ cho cùng một ValueError mà chúng ta đã thấy trước đó:

(x > 4) and (x < 8)
---------------------------------------------------------------------------ValueError                                Traceback (most recent call last)<ipython-input-40-3d24f1ffd63d> in <module>()----> 1 (x > 4) and (x < 8)ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

Vì thế hãy nhớ rằng: andor thực hiện một phép đánh giá Boolean duy nhất trên một đối tượng toàn diện, trong khi &| thực hiện nhiều phép đánh giá Boolean trên nội dung (các bit hoặc byte cá nhân) của một đối tượng.Đối với các mảng Boolean NumPy, thì thường phép toán sau là phép toán được mong muốn.