6.6. การ index และการ slice

ndarray สามารถระบุที่อยู่ได้สี่วิธี: index เดี่ยว, slice, boolean mask, และรูปแบบการกำหนดค่าของแต่ละอย่าง

6.6.1. Elements เดี่ยว

การ index ด้วยวงเล็บเหลี่ยมคืนค่าที่ตำแหน่งที่กำหนด:

a = np.arange(10, dtype=np.uint8)
print(a[0], a[-1])      # 0 9
print(a[1], a[-2])      # 1 8

index ติดลบนับจากท้าย เหมือนกับ Python list index ที่อยู่นอกช่วงจะยก IndexError

สำหรับ array rank สูงกว่า แต่ละแกนจะรับ index ค่า index จะอยู่ในวงเล็บชุดเดียวคั่นด้วยเครื่องหมายจุลภาค:

m = np.arange(9, dtype=np.uint8).reshape((3, 3))
print(m[1, 1])          # 4
print(m[2, 0])          # 6

เมื่อระบุ index น้อยกว่าจำนวนแกน แกนที่ไม่ได้ระบุ index จะถูกคงไว้ ผลลัพธ์คือ view แบบ reduced-rank ของแหล่งที่มา:

print(m[0])             # the first row, as a 1-D view of m

6.6.2. Slice

slice start:stop:step คืนค่า view ของ array view นั้นแบ่งปัน data buffer พื้นฐานกับแหล่งที่มา การเขียนผ่าน view จะเขียนไปยังแหล่งที่มาด้วย:

a = np.arange(10, dtype=np.uint8)
v = a[::2]              # array([0, 2, 4, 6, 8], dtype=uint8)
v[0] = 99
print(a)
# array([99, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8)

เมื่อต้องการ buffer อิสระ copy() จะสร้างขึ้นอย่างชัดเจน

การ slice ขยายตัวได้ตามธรรมชาติสู่มิติสูงกว่า แต่ละแกนจะมี slice ของตัวเอง:

m = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9]], dtype=np.uint8)

m[0]            # first row
m[0, :2]        # first two elements of row 0
m[:, 0]         # column 0 (still 2-D in ulab)
m[-1]           # last row
m[::2, ::2]     # every other row, every other column

การผสมจำนวนเต็ม (index เดี่ยว ที่ตัดแกนออก) และ slice (ที่คงแกนไว้) เป็นไปได้และเป็นวิธีปกติในการเข้าถึงแถวเดี่ยว / คอลัมน์เดี่ยว

6.6.3. Boolean mask

boolean array ที่มี shape เดียวกับแหล่งที่มาจะเลือก elements ที่ mask เป็น True boolean indexing ในปัจจุบันทำงานกับ array 1 มิติ; input rank สูงกว่าจะยก NotImplementedError

a = np.arange(9, dtype=np.float)
mask = a < 5
print(a[mask])

ผลลัพธ์:

array([0.0, 1.0, 2.0, 3.0, 4.0], dtype=float)

mask คือ bool ndarray ธรรมดา ดังนั้นนิพจน์ใดก็ตามที่ให้ผลเป็น mask ก็ใช้ได้:

b = np.array([4, 4, 4, 3, 3, 3, 13, 13, 13], dtype=np.uint8)
a = np.arange(9, dtype=np.uint8)
print(a[a * a > np.sin(b) * 100.0])

boolean indexing คืนค่า copy elements ที่เลือกอยู่ที่ตำแหน่งใดก็ตามที่ mask เป็น True -- ไม่ใช่ที่ stride สม่ำเสมอผ่านแหล่งที่มา -- ดังนั้นจึงไม่มี descriptor ที่ view สามารถใช้ระบุที่อยู่ได้ และผลลัพธ์จะถูก materialize ลงใน buffer ของตัวเอง

6.6.4. Integer-array indexing

การส่ง list หรือ array ของ index ในวงเล็บจะเลือก elements เหล่านั้นออกมาในขั้นตอนเดียว:

a = np.array([10, 20, 30, 40, 50], dtype=np.uint8)
a[[0, 2, 4]]
# array([10, 30, 50], dtype=uint8)

ผลลัพธ์คือ copy; elements ที่เลือกไม่แบ่งปันพื้นที่เก็บข้อมูลกับแหล่งที่มาอีกต่อไป รูปแบบเดียวกันทำงานทางด้านซ้ายของการกำหนดค่า:

a[[0, 2, 4]] = 0
# array([0, 20, 0, 40, 0], dtype=uint8)

take() (อธิบายใน การเลือกและการจัดเรียงใหม่) คือรูปแบบฟังก์ชันของการดำเนินการเดียวกันและรับ keyword out= สำหรับการใช้งานโดยไม่จัดสรรหน่วยความจำในลูปสตรีมมิง

6.6.5. การกำหนดค่าผ่าน slice

slice และ mask ปรากฏทางด้าน ซ้าย ของการกำหนดค่าเช่นเดียวกับด้านขวา ด้านขวาอาจเป็น scalar, array อื่น, หรือ view:

m = np.zeros((3, 3), dtype=np.uint8)
m[0]      = 1            # whole row 0 set to 1
m[:, 2]   = 3            # whole column 2 set to 3
m[1, 1:3] = [7, 8]       # row 1, columns 1 and 2

boolean mask ทางด้านซ้ายจะแทนที่ elements ที่ตรงตามเงื่อนไข:

a = np.arange(9, dtype=np.uint8)
a[a < 3] = 99
# array([99, 99, 99, 3, 4, 5, 6, 7, 8], dtype=uint8)

a = np.arange(9, dtype=np.uint8)
b = np.array(range(9)) + 12
a[b < 15] = b[b < 15]
# array([12, 13, 14, 3, 4, 5, 6, 7, 8], dtype=uint8)

6.6.6. ทำไมการกำหนดค่าผ่าน slice จึงสำคัญบน camera

การกำหนดค่าผ่าน slice เขียนทับ array ที่มีอยู่แล้ว ไม่มีการจัดสรร array ใหม่ นั่นคือความแตกต่างระหว่าง:

out = a + b              # makes a new array the size of a
out = out * 2            # makes another new array

และ:

out[:] = a               # writes into the existing out
out   += b               # in place
out   *= 2               # in place

เวอร์ชันแรกขอ RAM จาก cam สำหรับ array ใหม่สองชุด; เวอร์ชันที่สองไม่ขออะไรเลย บน microcontroller ที่มี RAM จำกัด ความแตกต่างนี้มักเป็นความแตกต่างระหว่าง script ที่ทำงานได้สบายๆ กับ script ที่หน่วยความจำไม่พอ

ประสิทธิภาพ อธิบายรูปแบบนี้อย่างละเอียด กฎสำคัญที่ควรรู้ในตอนนี้คือ การกำหนดค่าผ่าน slice, ตัวดำเนินการคณิตศาสตร์ in-place (+=, *=, ...), และ keyword out= บน universal function คือสามเครื่องมือที่ทำให้การอัปเดตโดยไม่จัดสรรหน่วยความจำเป็นไปได้