6.11. Phép rút gọn (Reductions)

Một phép rút gọn (reduction) thu gọn một mảng theo một hoặc nhiều trục bằng cách tính tổng, tính trung bình, lấy giá trị nhỏ nhất, v.v. Mỗi phép rút gọn là một lần gọi thư viện duy nhất trên toàn bộ mảng, nhanh hơn nhiều so với vòng lặp Python tương đương. numpy cung cấp các phép rút gọn thông dụng nhất:

  • sum() -- tổng của tất cả phần tử

  • mean() -- giá trị trung bình số học (tổng chia cho số phần tử)

  • std() -- độ lệch chuẩn, ddof= điều chỉnh mẫu số (N - ddof)

  • min() / max() -- phần tử nhỏ nhất và lớn nhất

  • median() -- giá trị giữa khi sắp xếp các phần tử theo thứ tự (phân vị thứ 50)

  • argmin() / argmax() -- chỉ số của phần tử nhỏ nhất hoặc lớn nhất

  • all() / any() -- phép rút gọn giá trị logic trên mảng boolean

6.11.1. Không có từ khóa axis

Khi gọi mà không có axis=, phép rút gọn trả về một giá trị vô hướng bao trùm toàn bộ mảng:

a = np.array([1, 2, 3, 4], dtype=np.float)
np.sum(a)           # 10.0
np.mean(a)          # 2.5
np.std(a)           # 1.118...
np.median(a)        # 2.5

b = np.array([40, 10, 30, 20], dtype=np.float)
np.max(b)           # 40.0
np.argmax(b)        # 0  (index of the maximum)

6.11.2. Với từ khóa axis

axis= thu gọn một trục được chỉ định và giữ nguyên các trục còn lại. Kết quả là một mảng có bậc thấp hơn đầu vào một bậc:

m = np.arange(12, dtype=np.float).reshape((3, 4))

np.sum(m)               # 66.0          - scalar
np.sum(m, axis=0)       # length-4      - column sums
np.sum(m, axis=1)       # length-3      - row sums

Quy tắc hình dạng tương tự áp dụng cho mọi phép rút gọn: axis=0 thu gọn trục đầu tiên, axis=1 thu gọn trục thứ hai, và cứ tiếp tục như vậy. Ví dụ, giá trị trung bình / độ lệch chuẩn theo từng hàng được viết là np.mean(m, axis=1)np.std(m, axis=1). Kết quả có độ dài bằng chiều dài của trục kia.

Từ khóa keepdims=True giữ nguyên trục đã thu gọn với độ dài 1 thay vì loại bỏ nó. Sự khác biệt này quan trọng khi kết quả rút gọn cần được phát (broadcast) trở lại với mảng gốc: keepdims giữ nguyên bậc, giúp các quy tắc phát (broadcasting) được căn chỉnh theo từng trục.

Trừ giá trị trung bình của từng hàng khỏi hàng đó là cách dùng điển hình:

m = np.arange(12, dtype=np.float).reshape((3, 4))
row_means = np.mean(m, axis=1, keepdims=True)
# row_means has shape (3, 1)
centred = m - row_means
# (3, 4) - (3, 1) -> (3, 4), each row centred on its own mean

Nếu không dùng keepdims, np.mean(m, axis=1) trả về kết quả 1-D có hình dạng (3,). Phép phát (broadcasting) (3, 4) - (3,) căn chỉnh (3,) thành (1, 3) sau khi thêm bậc vào đầu, điều này không tương thích với (3, 4): các trục cuối không khớp (4 so với 3) và không có trục nào bằng 1, do đó numpy phát sinh ValueError. keepdims=True giúp phép trừ vẫn hợp lệ.

6.11.3. Bố cục bộ nhớ quan trọng

Kết hợp với bố cục row-major được trình bày tại Hình dạng và stride, rút gọn theo trục cuối là trường hợp tiết kiệm nhất. Phép rút gọn duyệt qua khối dữ liệu theo chiều được lưu trữ, không cần nhảy từ hàng này sang hàng khác:

m = np.arange(2000, dtype=np.float).reshape((2, 1000))
np.sum(m, axis=1)       # cheap - long axis is the inner one
np.sum(m, axis=0)       # has to jump rows on every step

Khi ứng dụng có thể chọn cách bố trí bộ đệm, hãy đặt trục dài ở cuối để các phép rút gọn trên đó chạy theo chiều nhanh.

6.11.4. Đầu vào là iterable

Hầu hết các phép rút gọn đều chấp nhận một iterable Python (một list, một range, hoặc tuple) thay cho ndarray. Sự tiện lợi này tốn thêm vài micro giây cho phép chuyển đổi ngầm -- và chi phí này tích lũy nhanh trong một vòng lặp. Khi cùng một dữ liệu được rút gọn nhiều lần, hãy tạo ndarray một lần rồi truyền đi dùng lại.