6.11. การลดค่า (Reductions)¶
การลดค่า (reduction) คือการยุบอาร์เรย์ตามแกนหนึ่งหรือหลายแกนด้วยการหาผลรวม ค่าเฉลี่ย ค่าต่ำสุด และอื่น ๆ การลดค่าแต่ละครั้งเป็นการเรียกไลบรารีเพียงครั้งเดียวต่ออาร์เรย์ทั้งหมด ซึ่งเร็วกว่าลูป Python ที่ทำงานเทียบเท่ากันมาก numpy ครอบคลุมฟังก์ชันที่ใช้บ่อยดังนี้:
sum()-- ผลรวมของทุกสมาชิกmean()-- ค่าเฉลี่ยเลขคณิต (ผลรวมหารด้วยจำนวนสมาชิก)std()-- ส่วนเบี่ยงเบนมาตรฐาน โดยddof=ปรับตัวหาร (N - ddof)median()-- ค่ากลางเมื่อเรียงสมาชิกจากน้อยไปมาก (เปอร์เซ็นไทล์ที่ 50)argmin()/argmax()-- ดัชนี ของสมาชิกที่น้อยที่สุดหรือมากที่สุด
6.11.1. ไม่ใช้คีย์เวิร์ด axis¶
เมื่อเรียกโดยไม่ระบุ axis= การลดค่าจะคืนค่าสเกลาร์ที่ครอบคลุมอาร์เรย์ทั้งหมด:
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. ใช้คีย์เวิร์ด axis¶
axis= จะหดแกนที่ระบุและคงแกนอื่นไว้ ผลลัพธ์เป็นอาร์เรย์ที่มีอันดับต่ำกว่าอินพุตหนึ่งระดับ:
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
กฎรูปร่างเดียวกันนี้ใช้กับการลดค่าทุกประเภท: axis=0 ยุบแกนแรก axis=1 ยุบแกนที่สอง และต่อไปเรื่อย ๆ ตัวอย่างเช่น ค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐานตามแถวเขียนเป็น np.mean(m, axis=1) และ np.std(m, axis=1) ผลลัพธ์จะมีความยาวตามแกน อื่น
คีย์เวิร์ด keepdims=True จะคงแกนที่ถูกยุบไว้โดยให้มีความยาว 1 แทนที่จะตัดออก ความแตกต่างนี้มีความสำคัญเมื่อผลลัพธ์ที่ลดค่าแล้วต้องการบรอดแคสต์กลับไปยังต้นฉบับ: keepdims รักษาอันดับไว้ ซึ่งทำให้กฎการบรอดแคสต์ยังคงจัดแนวแกนต่อแกนได้ถูกต้อง
การลบค่าเฉลี่ยของแต่ละแถวออกจากแถวนั้นเองเป็นตัวอย่างการใช้งานที่เป็นแบบแผน:
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
หากไม่มี keepdims np.mean(m, axis=1) จะคืนผลลัพธ์แบบ 1-D ที่มีรูปร่าง (3,) การบรอดแคสต์ (3, 4) - (3,) จะจัด (3,) เป็น (1, 3) หลังจากเติมอันดับ ซึ่งไม่เข้ากันกับ (3, 4): แกนสุดท้ายไม่ตรงกัน (4 กับ 3) และไม่มีแกนใดเป็น 1 ดังนั้น numpy จะเกิด ValueError keepdims=True คือสิ่งที่ทำให้การลบยังคงถูกต้อง
6.11.3. เรื่องของการจัดวางหน่วยความจำ¶
เมื่อใช้ร่วมกับการจัดวางแบบ row-major ที่อธิบายไว้ใน Shape และ stride การลดค่าตามแกน สุดท้าย เป็นกรณีที่ประหยัดที่สุด การลดค่าจะวนซ้ำบล็อกข้อมูลในทิศทางที่จัดเก็บโดยไม่มีการข้ามจากแถวหนึ่งไปยังอีกแถว:
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
เมื่อแอปพลิเคชันมีทางเลือกในการจัดวางบัฟเฟอร์ ให้วางแกนยาวไว้สุดท้ายเพื่อให้การลดค่าตามแกนนั้นทำงานในทิศทางที่รวดเร็ว
6.11.4. อิเทอเรเบิลเป็นอินพุต¶
การลดค่าส่วนใหญ่รับอิเทอเรเบิลของ Python (list, range, tuple) แทน ndarray ได้ ความสะดวกนี้มีค่าใช้จ่ายเพียงไม่กี่ไมโครวินาทีสำหรับการแปลงโดยนัย ซึ่งสะสมได้รวดเร็วในลูป เมื่อข้อมูลเดียวกันถูกลดค่าหลายครั้ง ให้สร้าง ndarray ครั้งเดียวแล้วส่งต่อไปใช้งาน