numpy เป็นมอดูลที่ทำให้เราสามารถใช้ออบเจ็กต์ชนิดที่เรียกว่า ndarray ซึ่งหมายถึงอาเรย์หลายมิติ บางครั้งก็เรียกว่าอาเรย์เฉยๆ
คำว่า "อาเรย์" นั้นเป็นคำที่ถูกใช้ในภาษาอื่นๆอีกหลายภาษา เช่น ภาษาซี, php, จาวาสคริปต์, รูบี เป็นต้น แต่ว่าสำหรับในภาษาไพธอนสิ่งที่มีคุณสมบัติเทียบเท่ากับอาเรย์ในภาษาอื่น นั้นกลับเรียกว่าลิสต์ ส่วนอาเรย์จะหมายถึง ndarray ของ numpy
ในมอดูลมาตรฐานของภาษาไพธอนก็มีสิ่งที่เรียกว่าอาเรย์อยู่ แต่ก็ไม่เป็นที่นิยมใช้ ดังนั้นพอพูดถึงอาเรย์ในภาษาไพธอนแล้วจึงมักหมายถึง ndarray ของ numpy นั่นเอง
ก่อนที่จะเริ่มใช้งานสิ่งที่ต้องทำเป็นอย่างแรกก็คือทำการ import เรียกใช้ขึ้นมาก่อน
import numpy as np
ตัวย่อ np นี้จะใช้แบบนี้ไปตลอดในบทความทุกบท เพราะค่อนข้างเป็นสากล แม้แต่เวลาที่เรียกชื่อฟังก์ชันต่างๆใน numpy ก็จะเรียกโดยขึ้นต้นด้วย np.
การสร้างอาเรย์ขึ้นจากลิสต์ มีอยู่หลายวิธีในการสร้างอาเรย์ แต่วิธีที่พื้นฐานที่สุดคือสร้างขึ้นมาจากลิสต์, ทูเพิล หรือเรนจ์ โดยใช้ np.array(ลิสต์)
aray = np.array(range(3,7))
araya = np.array([[1,2],[3,4]])
print(aray)
print(araya)
ได้
[3 4 5 6]
[[1 2]
[3 4]]
เท่านี้ก็จะได้อาเรย์ขึ้นมาตามที่ต้องการ ถ้าดูเผินๆจะเห็นว่าไม่ต่างอะไรจากลิสต์นัก แต่ความจริงแล้วต่างไปพอสมควร
ที่อาจจะเห็นได้เป็นอย่างแรกก็คือเวลาที่สั่ง print จะออกมาเป็นแถวเป็นระเบียบร้อยอย่างที่เห็นโดยอัตโนมัติ ทำให้ดูเข้าใจง่ายขึ้นมาก
อย่างต่อมาคืออาเรย์มีแอตทริบิวต์ติดตัวที่สามารถให้ข้อมูลของตัวอาเรย์นั้น เช่น
shape |
รูปร่างของอาเรย์ |
size |
จำนวนสมาชิกในอาเรย์ |
ndim |
จำนวนมิติของอาเรย์ |
ลองเอาอาเรย์ ๒ ตัวจากตัวอย่างเมื่อครู่มาหาแอตทริบิวต์
print(aray.shape) # ได้ (4,)
print(aray.size) # ได้ 4
print(aray.ndim) # ได้ 1
print(araya.shape) # ได้ (2, 2)
print(araya.size) # ได้ 4
print(araya.ndim) # ได้ 2
นอกจากนี้ยังสามารถใช้ฟังก์ชันที่ชื่อเหมือนกับแอตทริบิวต์เหล่านี้ในการหาค่าได้ด้วย ผลที่ได้จะเหมือนกัน
print(np.size(araya))
print(np.shape(araya))
print(np.ndim(araya))
จะเห็นว่ากรณีที่ลิสต์ที่ใช้นั้นมีการซ้อนกันก็จะได้เป็นอาเรย์ ๒ มิติ โดยมีแนวตั้งเป็นมิติที่หนึ่ง แนวนอนเป็นมิติที่สอง
และยังสามารถซ้อนกันเป็นมิติที่สูงขึ้นไปอีกได้ เช่นลองสร้าง ๓ มิติ
araye = np.array([[[1,2],[3,4]],[[5,6],[7,8]]])
print(araye)
print(araye.shape)
print(araye.size)
print(araye.ndim)
ได้
[[[1 2]
[3 4]]
[[5 6]
[7 8]]]
(2, 2, 2)
8
3
โดยมิติที่สามนั้นหากนำมาวาดเป็นรูปแล้วละก็อาจแสดงได้ในรูปของแถวในแนวลึกที่เพิ่มเข้ามา
สำหรับ สี่มิติขึ้นไปจะเริ่มอยู่เหนือสามัญสำนึกของคนทั่วไป งานที่จะต้องใช้มิติมากขนาดนั้นก็มักจะค่อนข้างเฉพาะทาง ดังนั้นจะไม่ขอกล่าวถึง
เนื้อหาส่วนใหญ่ในบทต้นๆจะเน้นหนึ่งถึงสองมิติเป็นหลัก และสำหรับสามมิติจะเริ่มเน้นใน
บทที่ ๒๓ มีข้อควรระวังคือลิสต์ที่จะใช้สร้างอาเรย์จะต้องมีจำนวนสมาชิกในลิสต์ย่อยแต่ละลิสต์เท่ากันไม่เช่นนั้นจะถูกตีความเป็นออบเจ็กต์ทั่วไป
arayu = np.array([[1,2],[3,4,5]])
print(arayu.shape) # ได้ (2,)
print(arayu.size) # ได้ 2
print(arayu.ndim) # ได้ 1
จะเห็นว่าผลที่ได้คือมันกลายเป็นอาเรย์มิติเดียวซึ่งมีขนาดเป็น 2 นั่นเพราะ [1,2] และ [3,4,5] ถูกตีความเป็นออบเจ็กต์อย่างละชิ้น แทนทีจะคิดเป็นตัวๆแยกกัน
ชนิดของข้อมูลในอาเรย์ สามารถตรวจสอบชนิดของข้อมูลในอาเรย์ได้โดยดูที่แอตทริบิวต์ชื่อ dtype
เช่นลองใช้อาเรย์ที่สร้างในตัวอย่างก่อน
araye = np.array([[[1,2],[3,4]],[[5,6],[7,8]]])
arayu = np.array([[1,2],[3,4,5]])
print(araye.dtype) # ได้ int64
print(arayu.dtype) # ได้ object
จะเห็นว่า arayu ซึ่งสร้างขึ้นมาจากลิสต์ที่มีจำนวนสมาชิกไม่เท่ากันนั้นได้ข้อมูลประเภท object แทนที่จะเป็น int อย่างที่ควรเป็น
ส่วน araye นั้นกลายเป็นชนิด int ตามที่ควรจะเป็น โดยเลข 64 ใน int64 นี้บอกถึงขนาดของหน่วยความจำเป็นบิตที่ใช้ในการเก็บตัวเลข ปกติแล้วในภาษาไพธอนจำนวนเต็มจะไม่ได้แบ่งชนิดย่อย แต่ในบางภาษาเช่นภาษาซีจำนวนเต็มจะถูกแบ่งเป็นชนิดตามขนาดของหน่วยความจำที่ ใช้
ปกติแล้วถ้าไม่ได้ระบุอะไรข้อมูลจำนวนเต็มจะถูกตั้งให้เป็น int64 ซึ่งเป็นขนาดใหญ่สุด นอกจาก int64 แล้วก็ยังมี int8 int16 int32 ซึ่งจะกินพื้นที่น้อยกว่า และ float เองก็มีแบ่งเช่นกัน
ชนิดของข้อมูลสามารถกำหนดได้ตอนที่สร้างอาเรย์ขึ้นมา โดยเพิ่มคีย์เวิร์ด dtype เข้าไป
ari = np.array([1,2,3,4],dtype='int16')
ariy = np.array([1,2,3,4],dtype='float32')
print(ariy) # ได้ [ 1. 2. 3. 4.]
ถ้าตอนสร้างมีสมาชิกที่มีทศนิยมแม้แต่ตัวเดียวทั้งหมดก็จะกลายเป็น float64 ทันที
arey = np.array([1,2,3.14,4])
print(arey) # ได้ [ 1. 2. 3.14 4. ]
print(arey.dtype) # ได้ float64
จะเห็นว่าข้อมูลในอาเรย์จะต้องประกอบจากตัวแปรชนิดเดียวกันหมดทุกตัว หากตอนที่สร้างอาเรย์ขึ้นจากลิสต์นั้นมีสมาชิกที่ชนิดต่างกันจะถูกทำให้เหมือนกันหมด
กรณีที่มีสายอักขระปนอยู่ตัวอื่นก็จะถูกเปลี่ยนเป็นสายอักขระไปด้วย เช่น
arayo = np.array([[1,2],[3.,'4']])
print(arayo)
print(arayo.dtype)
ได้
[['1' '2']
['3.0' '4']]
<U32
U ในที่นี้หมายถึงเป็นยูนิโค้ด และ 32 เป็นจำนวนหน่วยความจำที่ใช้ โดยปกติจะเท่ากับจำนวนตัวอักษรแต่ถ้าสั้นกว่า 32 จะถูกกำหนดให้เป็น 32
ใน ภาษาซีเวลาที่ประกาศตัวแปรชนิดสายอักขระจะต้องกำหนดความยาวไว้ตายตัว ดังนั้นอาเรย์ซึ่งตั้งอยู่บนพื้นฐานของภาษาซีจึงมีลักษณะการจัดเก็บข้อมูลใน ลักษณะนี้ไปด้วย
อนึ่ง
ความยาวของสายอักขระสามารถกำหนดขึ้นเองได้ และถ้ากำหนดความยาวต่ำกว่าจำนวนตัวอักษรก็จะถูกตัดทอนหายไป
print(np.array([[123456789]],dtype='<U5')) # ได้ [['12345']]
นี่เป็นเนื้อหาที่เกี่ยวเนื่องมาจากภาษาซี จะไม่เน้นมาก ณ ที่นี้เพราะงานหลักของอาเรย์ที่จะใช้คือใช้กับตัวเลขเพื่อคำนวณ
อาเรย์สามารถเก็บข้อมูลชนิดสายอักขระได้ก็จริง แต่โดยทั่วไปมักจะใช้กับจำนวนตัวเลขมากกว่า เพื่อที่จะใช้ประโยชน์ในเรื่องการคำนวณอย่างเต็มที่
ชนิดของอาเรย์ทั้งหมดสามารถดูได้ที่แอตทริบิวต์ np.sctypes
print(np.sctypes)
ผลลัพธ์
{'int': [<class 'numpy.int8'>, <class 'numpy.int16'>, , <class 'numpy.int64'>], 'uint': [, <class 'numpy.uint16'>, , <class 'numpy.uint64'>], 'others': [, <class 'object'>, <class 'str'>, , <class 'numpy.void'>], 'float': [, <class 'numpy.float32'>, , <class 'numpy.float128'>], 'complex': [<class 'numpy.complex64'>, <class 'numpy.complex128'>, <class 'numpy.complex256'>]}
ชนิดของสมาชิกในอาเรย์สามารถเปลี่ยนได้ด้วยเมธอด astype บนตัวอาเรย์
x = np.array([3.5,4.7,9,11.3,15])
print(x) # ได้ [ 3.5 4.7 9. 11.3 15. ]
print(x.astype(int)) # ได้ [ 3 4 9 11 15]
print(x.astype(str)) # ได้ ['3.5' '4.7' '9.0' '11.3' '15.0']
การอ้างอิงถึงข้อมูลในอาเรย์ เช่น เดียวกับลิสต์ อาเรย์ก็ใช้การเติมวงเล็บเหลี่ยม [ ] เพื่ออ้างอิงข้อมูล โดยเลขในกรณีสองมิตินั้น ตัวแรกคือดัชนีของแนวตั้ง (เลขแถว) และตัวหลังคือแนวนอน (เลขหลัก)
ariyu = np.array([[1,2,3],[4,5,6]])
print(ariyu[0][1]) # ได้ 2
print(ariyu[1][2]) # ได้ 6
แต่นอกจากนั้นแล้วยังสามารถเขียนโดยใช้เป็นคู่อันดับโดยมีจุลภาค , คั่นในวงเล็บเหลี่ยมตัวเดียวแทนการใส่วงเล็บเหลี่ยมสองอันได้ด้วย ซึ่งแบบนี้ลิสต์ไม่สามารถทำได้
print(ariyu[0,2]) # ได้ 3
print(ariyu[1,1]) # ได้ 5
การเข้าถึงสมาชิกทีละหลายตัวก็ทำได้ด้วยการใช้โคลอน : เช่นเดียวกับลิสต์ แต่สำหรันอาเรย์สองมิติแทนที่จะใช้วงเล็บเหลี่ยมวางต่อกันสองอันสามารถใช้จุลภาคแทนได้เช่นกัน
print(ariyu[1][:]) # ได้ [4 5 6]
print(ariyu[1,:]) # ได้ [4 5 6]
ซึ่งการที่ใช้จุลภาคแทนได้นั้นมีข้อดีคือเข้าถึงสมาชิกที่อยู่ในหลัก (แนวตั้ง) เดียวกันแต่คนละแถว (แนวนอน) โดยการใส่ : ไว้ทางซ้าย
print(ariyu[:,1]) # ได้ [2 5]
ซึ่งจะเห็นได้ว่าการใช้วงเล็บเหลี่ยมคู่ [ ][ ] จะวาง [:] ไว้ซ้ายหรือขวาก็ให้ผลไม่ต่างจากเดิม คือให้สมาชิกที่อยู่ในแถวแนวนอนแถวเดียวกันเท่านั้น
print(ariyu[:][1]) # ได้ [4 5 6]]
จะเห็นว่า [1,:] กับ [1][:] และ [:][1] ให้ผลเหมือนกัน แต่ [:,1] เท่านั้นที่จะให้ผลต่าง
ถ้าลองสร้างลิสต์แบบเดียวกันมาโดยไม่แปลงเป็นอาเรย์จะพบว่าไม่สามารถทำในสิ่งเดียวกันได้
liyu = [[1,2,3],[4,5,6]]
print(liyu[:][1]) # ได้ [4 5 6]
print(liyu[:,1]) # ได้ TypeError: list indices must be integers or slices, not tuple
สรุปเป็นตารางเพื่อให้เห็นภาพชัด สมมุติว่าอาเรย์มี n แถว m หลัก
แถว/หลัก |
0 |
1 |
2 |
3 |
.. |
m-1 |
0 |
[0,0] |
[0,1] |
[0,2] |
[0,3] |
[0,..] |
[0,m-1] |
1 |
[1,0] |
[1,1] |
[1,2] |
[1,3] |
[1,..] |
[1,m-1] |
2 |
[2,0] |
[2,1] |
[2,2] |
[2,3] |
[2,..] |
[2,m-1] |
3 |
[3,0] |
[3,1] |
[3,2] |
[3,3] |
[3,..] |
[3,m-1] |
.. |
[..,0] |
[..,1] |
[..,2] |
[..,3] |
[..,..] |
[..,m-1] |
n-1 |
[n-1,0] |
[n-1,1] |
[n-1,2] |
[n-1,3] |
[n-1,..] |
[n-1,m-1] |
สำหรับสามมิติซึ่งมีขนาด 3,3,3 อาจเขียนได้ดังนี้
การวางตัวเลขไว้หน้าและหลัง : เพื่อคัดเลือกอาเรย์เฉพาะในช่วงที่ต้องการก็ทำได้เช่นเดียวกับลิสต์
ตัวอย่างกรณีหนึ่งมิติ