ในบทที่ผ่านๆมาเราพูดถึงแต่ข้อมูลที่เป็นสองมิติเป็นหลัก แต่ต่อจากนี้ไปจะพูดถึงข้อมูลที่มีมิติเพิ่มขึ้นมาอีกมิติ
แนวความคิดเรื่องการแสดงข้อมูลสามมิติในสองมิติ ปกติ แล้วการวาดกราฟเส้นคือการแสดงความสัมพันธ์ระหว่างปริมาณ ๒ ค่าว่า เช่นตำแหน่งเทียบกับเวลา (กราฟ t-x) หรือตำแหน่งบนพื้นสองมิติ (กราฟ x-y)
ปัญหาที่มีตัวแปรอยู่แค่สองตัวแบบนี้สามารถเขียนด้วยกราฟเส้นในสองมิติเพื่อให้เห็นภาพชัดได้ง่าย
แต่ในกรณีที่ต้องการแสดงความสัมพันธ์ระห่างปริมาณ ๓ ค่านั้น ปัญหาจะเพิ่มขึ้นมาอีกมิตินึง
เช่นสมมุติว่าต้องการหาความสูงของพื้นที่ในบริเวณต่างๆเราอาจแสดงโดยการเขียนค่าตัวเลขออกมาเป็นอาเรย์
ตัวอย่าง สร้างอาเรย์ที่เก็บความสูงตามแนวแกน x และ y โดยสุ่มค่าเอา
import numpy as np
khwamsung = np.random.randint(0,100,(10,10))
ได้
[[96 36 88 85 64 91 0 60 92 11]
[47 92 76 85 66 16 44 19 62 27]
[41 40 61 23 63 38 23 38 48 96]
[65 8 68 62 47 82 76 76 78 17]
[68 18 21 65 61 58 42 14 99 89]
[81 59 38 84 60 56 44 91 16 50]
[29 85 24 52 47 3 14 6 12 88]
[ 9 87 59 41 53 13 48 58 37 16]
[80 11 1 30 43 35 55 41 31 28]
[48 92 18 24 12 2 79 84 73 38]]
จากอาเรย์นี้พอมองดูตัวเลขก็จะสามารถรู้ได้ว่าบริเวณไหนสูงแค่ไหน แต่ว่าเป็นตัวเลขแบบนี้จะมองให้เห็นภาพคงจะยาก
วิธีที่ดีที่สุดก็คือนำค่าความสูงนี้มาวาดเป็นความสูงของพื้นจริงๆในสามมิติ แบบนั้นจะเห็นภาพได้ชัดที่สุด
แน่ในบางสถานการณ์ก็ไม่เหมาะที่จะวาดภาพสามมิติ เพราะมีข้อจำกัดหลายอย่าง
matplotlib สามารถวาดภาพสามมิติได้ แต่นั่นเป็นเรื่องในระดับที่สูงขึ้นไปอีก ยังไม่ใช่เนื้อหาที่จะพูดถึงตอนนี้
แทนที่จะวาดเป็นสามมิติจริงๆ เราอาจแค่แสดงโดยทำให้มีอะไรบางอย่างเปลี่ยนแปลงไปตามค่าตัวเลข
วิธีหนึ่งที่ดีก็คือ การใช้สีแทนตัวค่าความสูง
การทำให้สีเปลี่ยนไปตามค่านั้นทำได้โดยให้ค่าเป็นตัวกำหนดความเข้มสีของแม่สีต่างๆ
ลองใช้แผนภาพการกระจายที่ได้พูดถึงไปใน
บทที่ ๑๖ ทำจุดไล่เรียงสี
import random
import matplotlib.pyplot as plt
x = range(10) # ให้ x ไล่ตั้งแต่ 0 ถึง 9
y = [0]*10 # ให้ y เป็น 0 ตลอด เพื่อจะให้จุดอยู่แนวเดียวกัน
c = [(1,random.random(),1) for i in range(10)] # ให้สัดส่วนของสีเขียวเปลี่ยนไปแบบสุ่ม
plt.figure(figsize=(8,2))
plt.scatter(x,y,c=c,s=1500)
plt.show()
จะได้จุดที่มีสีต่างไปตั้งแต่ขาวจนถึงม่วง ถ้าคิดซะว่าความม่วงของจุดแทนส่วนสูงก็จะได้ว่าแผนภาพนี้แสดงความสูงของแต่ละจุด
อย่างไรก็ตาม การต้องมาสร้างลิสต์ของแม่สีทั้งสามแบบนี้ตลอดเพื่อมาใช้นั้นดูแล้วยุ่งยาก
ดังนั้นอาจเป็นการสะดวกที่จะใช้สิ่งที่เรียกว่า คัลเลอร์แม็ป (color map)
คัลเลอร์แม็ป คัลเลอร์แม็ปคือฟังก์ชันชนิดหนึ่งที่เมื่อเราใส่ค่าตัวเลขลงไปแล้วจะคืนค่าออกมาเป็นค่าสี
ใน matplotlib ได้เตรียมฟังก์ชันคัลเลอร์แม็ปเอาไว้หลายตัว สามารถหาได้ด้วยฟังก์ชัน plt.get_cmap
ตัวอย่าง
print(plt.get_cmap('spring')(1.)) # ได้ (1.0, 1.0, 0.0, 1.0)
print(plt.get_cmap('spring')(0.75)) # ได้ (1.0, 0.75294117647058822, 0.24705882352941178, 1.0)
print(plt.get_cmap('spring')(0.5)) # ได้ (1.0, 0.50196078431372548, 0.49803921568627452, 1.0)
print(plt.get_cmap('spring')(0.25)) # ได้ (1.0, 0.25098039215686274, 0.74901960784313726, 1.0)
print(plt.get_cmap('spring')(0.)) # ได้ (1.0, 0.0, 1.0, 1.0)
print(plt.get_cmap('hot')(0.)) # ได้ (0.041599999999999998, 0.0, 0.0, 1.0)
ใน ที่นี้ plt.get_cmap('spring')() คือฟังก์ชันอันหนึ่ง ซึ่งรับค่าตัวเลขเป็นอาร์กิวเมนต์แล้วคืนค่าสีออกมา เลข ๔ ค่าคือ (แดง,เขียว,น้ำเงิน,ความทึบแสง)
ส่วน plt.get_cmap('hot')() ก็เป็นฟังก์ชันคัลเลอร์แม็ปอีกตัวหนึ่ง ใส่ค่าตัวเลขลงไปตัวนึงแล้วก็จะได้เลข ๔ ค่าออกมาเช่นกัน แต่จะต่างจาก spring
ถ้าใส่ชื่ออื่นลงไปก็จะได้ฟังก์ชันคัลเลอร์แม็ปที่แตกต่างกันออกไป ชื่อคัลเลอร์แม็ปชนิดต่างๆนั้นมีกำหนดไว้ตายตัวอยู่แล้ว
รายการคัลเลอร์แม็ปทั้งหมด เข้าไปดูแล้วเลือกสีตามที่ต้องการกันได้
http://matplotlib.org/examples/color/colormaps_reference.html นอกจาก นี้เรายังสามารถสร้างคัลเลอร์แม็ปขึ้นเองได้ด้วย เพียงแต่ค่อนข้างยุ่งยากจึงจะขอไม่พูดถึงตรงนี้ จากนี้ไปเราจะใช้คัลเลอร์แม็ปที่มีอยู่เป็นหลัก
ขอบเขตของค่าที่ใส่ ลงในฟังก์ชันคือจำนวนจริงตั้งแต่ 0 ถึง 1 หากลองไล่ค่าในช่วงนี้มาใส่ในฟังก์ชันจะได้ค่าสีที่เปลี่ยนไปเรื่อยๆ แต่ถ้าเกินจาก 1 ไปแล้วค่าจะไม่เปลี่ยนแปลงอีกแล้ว
for c in np.arange(0,1.6,0.1):
print('%s: %s'%(c,plt.get_cmap('winter')(c)))
ผลลัพธ์
0.0: (0.0, 0.0, 1.0, 1.0)
0.1: (0.0, 0.098039215686274508, 0.9509803921568627, 1.0)
0.2: (0.0, 0.20000000000000001, 0.90000000000000002, 1.0)
0.3: (0.0, 0.29803921568627451, 0.85098039215686272, 1.0)
0.4: (0.0, 0.40000000000000002, 0.80000000000000004, 1.0)
0.5: (0.0, 0.50196078431372548, 0.74901960784313726, 1.0)
0.6: (0.0, 0.59999999999999998, 0.69999999999999996, 1.0)
0.7: (0.0, 0.70196078431372544, 0.64901960784313728, 1.0)
0.8: (0.0, 0.80000000000000004, 0.59999999999999998, 1.0)
0.9: (0.0, 0.90196078431372551, 0.5490196078431373, 1.0)
1.0: (0.0, 1.0, 0.5, 1.0)
1.1: (0.0, 1.0, 0.5, 1.0)
1.2: (0.0, 1.0, 0.5, 1.0)
1.3: (0.0, 1.0, 0.5, 1.0)
1.4: (0.0, 1.0, 0.5, 1.0)
1.5: (0.0, 1.0, 0.5, 1.0)
การนำคัลเลอร์แม็ปมาใช้กับกราฟและแผนภาพ การใช้คัลเลอร์แม็ปในการวาดกราฟทำได้โดยใส่ฟังก์ชันคัลเลอร์แม็ปที่ต้องการลงในคีย์เวิร์ด cmap ในฟังก์ชันที่ใช้วาด
ขอยกตัวอย่างโดยเอาตัวอย่างที่แล้วมาแก้เล็กน้อยโดยเปลี่ยนค่า c ให้เป็นเลขสุ่มตัวเดียวแทนที่จะเป็นทูเพิลของเลขสีทั้ง ๓ แล้วก็ใส่
x = range(10)
y = [0]*10
c = [random.random() for i in range(10)] # เลขสุ่มในช่วง 0 ถึง 1
plt.figure(figsize=(8,2))
plt.scatter(x,y,c=c,s=1500,cmap=plt.get_cmap('PuRd')) # เพิ่ม cmap เข้าไป
plt.show()
จะพบว่าได้ผลออกมาคล้ายเมื่อกี้ เพราะ PuRd เป็นคัลเลอร์แม็ปที่ไล่สีในโทนขาวแดงชมพู
และจะเห็นว่าง่ายกว่าเดิมเพราะไม่ต้องใส่ค่าตัวเลขแม่สีทั้ง ๓ ตัว แค่ใส่เลขตัวเดียวแล้วมันก็จะเข้าสู่ฟังก์ชันคัลเลอร์แม็ปเพื่อเปลี่ยนเป็น ค่าสีทั้ง ๓ (และค่าความโปร่งใส) ให้โดยอัตโนมัติ
นอกจากนี้จริงๆ แล้ว cmap=plt.get_cmap('PuRd') สามารถย่อเหลือแค่ cmap='PuRd' ได้เลย ถ้าค่าที่ใส่เป็นสายอักขระโปรแกรมจะทำการค้นคัลเลอร์แม็ปที่มีชื่อนี้มาใช้ อยู่แล้ว
ในตัวอย่างนี้เราใส่ค่าสี (c) เป็นเลขในช่วง 0 ถึง 1 แต่ความจริงแล้วค่าสีไม่จำเป็นต้องอยู่ในช่วง 0 ถึง 1 ก็ได้ แม้ว่าค่าของฟังก์ชันคัลเลอร์แม็ปจะมีการเปลี่ยนแปลงเฉพาะในช่วง 0 ถึง 1 ก็ตาม
นั่นเพราะเวลาที่ใช้งานจริงๆค่าทั้งหมดจะถูกปรับค่าให้อยู่ใน ช่วง 0 ถึง 1 โดยอัตโนมัติ โดยค่าต่ำสุดจะกลายเป็น 0 และค่าสูงสุดจะกลายเป็น 1 ส่วนค่าระหว่างกลางก็จะถูกเทียบสัดส่วนไป
การปรับค่าทั้งหมดให้อยู่ในช่วง 0 ถึง 1 แบบนี้เรียกว่าการนอร์มาไลซ์ (normalize)
ค่านอร์มาไลซ์ = (ค่าเดิม - ค่าต่ำสุด)/(ค่าสูงสุด - ค่าต่ำสุด)
ดังนั้นเราจึงได้ค่าสีที่ไล่จากต่ำสุดจนสูงสุดเสมอ ไม่ต้องมาแปลงเอง
การ ปรับอัตโนมัติแบบนี้สะดวกดี แต่บางครั้งเราก็อาจไม่ได้ต้องการแบบนั้น หากต้องการปรับรูปแบบการแปลงค่าเอาเองก็สามารถทำได้ จะขอยกเรื่องนี้ไปพูดใน
บทหน้า การใส่แท่งแถบสี จากตัวอย่างที่แล้ว เพื่อให้เห็นการไล่เรียงสีชัด คราวนี้ลองเพิ่มแท่งที่มีการไล่เรียงสีตามลำดับเข้าไป
x = range(10)
y = [0]*10
c = [random.random() for i in range(10)]
plt.figure(figsize=(8,2))
plt.axes(ylim=[-4.5,1.5])
plt.scatter(x,y,c=c,s=1500,cmap='PuRd')
plt.scatter(x,[-3]*10,c=x,s=1500,marker='s',cmap='PuRd')
plt.show()
ทำแบบนี้ด้านล่างก็จะได้แท่งที่มีการจัดเรียงสีตามแบบของ PuRd
พอเห็นการจัดเรียงเป็นแท่งแบบนี้แล้วทำให้เราสารถรู้ได้ง่ายว่าสีอะไรมีค่าน้อยหรือมาก
ที่จริงแล้ว matplotlib มีคำสั่งที่สร้างแท่งไล่เรียงสีที่ใช้อธิบายสีในคัลเลอร์แม็ป นั่นคือ plt.colorbar()
ลองพิมพ์ตามนี้เพิ่มเข้าไป
plt.colorbar()
จากนั้นก็จะมีแถบสีปรากฏทางขวา
แถบสีนี้เป็นสิ่งที่ควรใส่ไว้ทุกครั้ง มีความสำคัญเพราะทำให้เข้าใจว่าสีอะไรแทนค่าเท่าไหร่
โดยปกติแล้วค่าที่เราใส่ลงไปในคีย์เวิร์ด c นั้นจะถูกนำไปเปลี่ยนสีโดยค่าต่ำสุดเท่ากับสีล่างสุด ค่าสูงสุดเท่ากับสีบนสุด ค่าระหว่างนั้นก็ไล่ไปเป็นเชิงเส้น ไม่ว่าค่าสูงสุดและต่ำสุดจะเป็นเท่าไหร่สีก็จะถูกไล่จากบนสุดไปล่างสุดเสมอ
ลองดูตัวอย่างเพิ่มเติม เอาตัวอย่างที่แล้วมาแก้ต่อโดยใช้ numpy แล้วก็เพิ่มเป็นร้อยจุด
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(100)
y = np.zeros(100)
c = np.random.randint(0,100,100)
plt.figure(figsize=(8,2))
sc = plt.scatter(x,y,c=c,s=50,marker='d',cmap='hot')
plt.colorbar()
plt.show()
แถบสีนั้นสามารถปรับแต่งอะไรต่างๆได้มากมายโดยการเติมคีย์เวิร์ดเข้าไป เราจะกลับมาพูดถึงการปรับแต่งแถบสีเพิ่มเติมอีกใน
บทที่ ๒๖ การเตรียมอาเรย์สองมิติสำหรับแทนพิกัด x,y หลังจากที่เข้าใจเรื่องคัลเลอร์แม็ปแล้ว และสามารถสร้างแผนภาพไล่สีในหนึ่งมิติได้แล้ว ต่อไปลองมาทำเป็นสองมิติดู
แต่ว่าการทำสองมิตินั้นมีความซับซ้อน เราจำเป็นจะต้องเตรียมอาเรย์ที่เก็บไล่เรียงตำแหน่งในแกน x และ y ไว้ควบคู่กัน ซึ่งวิธีการสร้างก็มีอยู่หลายวิธี
วิธีหนึ่งอาจใช้ tile repeat reshape
x = np.tile(np.arange(10),(10,1))
y = np.repeat(np.arange(10),10).reshape(10,10)
print(x)
print(y)
ได้
[[0 1 2 3 4 5 6 7 8 9]
[0 1 2 3 4 5 6 7 8 9]
[0 1 2 3 4 5 6 7 8 9]
[0 1 2 3 4 5 6 7 8 9]
[0 1 2 3 4 5 6 7 8 9]
[0 1 2 3 4 5 6 7 8 9]
[0 1 2 3 4 5 6 7 8 9]
[0 1 2 3 4 5 6 7 8 9]
[0 1 2 3 4 5 6 7 8 9]
[0 1 2 3 4 5 6 7 8 9]]
[[0 0 0 0 0 0 0 0 0 0]
[1 1 1 1 1 1 1 1 1 1]
[2 2 2 2 2 2 2 2 2 2]
[3 3 3 3 3 3 3 3 3 3]
[4 4 4 4 4 4 4 4 4 4]
[5 5 5 5 5 5 5 5 5 5]
[6 6 6 6 6 6 6 6 6 6]
[7 7 7 7 7 7 7 7 7 7]
[8 8 8 8 8 8 8 8 8 8]
[9 9 9 9 9 9 9 9 9 9]]
จะเห็นว่า x เปลี่ยนค่าไปตามแนวนอน ส่วน y เปลี่ยนค่าไปตามแนวตั้ง แต่จะเหมือนเดิมตลอดในอีกแนว
นี่คืออาเรย์ที่เราต้องการ มีไว้ใช้แทนพิกัดในแกน x และ y สำหรับในแต่ละจุด
ที่จริงในมอดูลมาตรฐานของไพธอนก็มีฟังก์ชันที่ช่วยให้ทำอะไรในลักษณะนี้ออกมา ได้ นั่นคือฟังก์ชัน product ในมอดูล itertools ซึ่งอธิบายไว้ในบทความ
ภาษาไพธอนเบื้องต้นบทที่ ๒๘ เพียงแต่ว่าผลที่ได้จะอยู่ในรูปหนึ่งมิติ ต้องมาแปลงเป็นอาเรย์แล้ว reshape อีกที
import itertools
xy = list(itertools.product(range(10),range(10)))
print(xy) # ได้ [(0, 0), (0, 1), (0, 2), (0, 3),...
x,y = np.array(xy).T.reshape(2,10,10)
ทำแบบนี้ก็จะได้ x กับ y เหมือนกันกับตัวอย่างที่แล้ว
แต่มีวิธีที่ทำได้ง่ายกว่าและนิยมมากกว่า นั่นคือการใช้ฟังก์ชัน np.meshgrid
หากเขียนเป็น
x,y = np.meshgrid(np.arange(10),np.arange(10))
ก็จะได้ x,y ในลักษณะแบบเดียวกับตัวอย่างข้างต้น
ฟังก์ชัน np.meshgrid มีไว้สำหรับสร้างลิสต์ของอาเรย์ที่แจกแจงค่า โดยออกมาในรูปของอาเรย์ที่มีมิติตามจำนวนอาร์กิวเมนต์ที่ใส่เข้าไป
ในที่นี้ใส่อาร์กิวเมนต์เป็น np.arange(10) ไป ๒ ตัว ซึ่ง ๒ ตัวนี้แทนแกน x และ y ผลที่ได้คือฟังก์ชันนี้จะคืนค่าอาเรย์ 10x10 มา ๒ ตัว ตัวแรกคือค่าของแกน x ตัวหลังคือของแกน y จากนั้นเราก็ใช้ตัวแปร x กับ y มารับค่าทั้งสองนี้
ดังนั้นด้วยฟังก์ชันนี้เราจะได้โครงข่ายของค่า x และ y ออกมา ต่อจากนี้ไปจะใช้ฟังก์ชันนี้เป็นหลัก จะใช้อีกบ่อยครั้ง ดังนั้นจำเป็นต้องทำความเข้าใจให้ดี
ทีนี้เมื่อได้โครงข่าย x และ y ตามที่ต้องการมาแล้วเราก็สร้างอาเรย์แทนความสูงซึ่งมีจำนวนแถวและหลักเท่ากันมา
z = np.random.randint(0,100,(10,10))
print(z)
ได้
[[93 43 63 56 68 71 15 16 40 16]
[25 42 64 5 14 61 49 77 81 34]
[77 1 78 96 93 81 11 33 45 26]
[84 61 95 61 6 86 74 31 45 67]
[10 28 73 4 9 34 2 34 37 70]
[33 8 99 20 78 50 24 7 81 57]
[56 62 16 69 64 44 14 49 78 92]
[31 87 25 29 97 12 69 78 65 68]
[12 67 23 82 32 37 25 23 13 83]
[94 93 79 10 5 3 15 10 42 74]]
ลองนำมาเรียงต่อกันดูจะทำให้เห็นภาพขึ้น ในที่นี้อันบนสุดคืออาเรย์แทนความสูง และสองอันล่างคือพิกัดในแกน x และ y
เท่านี้ส่วนประกอบที่เราต้องการนำมาใช้วาดแผนภาพไล่สีก็พร้อมแล้ว
ต่อมาลองนำมาใช้วาดกราฟการแจกแจงเพื่อแสดงความสูงของพื้นที่ในสองมิติกันดูเลย
x,y = np.meshgrid(np.arange(10),np.arange(10)) # สร้างโครงข่ายแกน x และ y
khwamsung = np.random.randint(0,100,(10,10)) # สุ่มความสูงในช่วง 0 ถึง 99
plt.axes(aspect=1,xlim=[-1,10],ylim=[-1,10])
plt.scatter(x,y,c=khwamsung,s=600,marker='s',cmap='BuGn') # ใช้สีโทนเขียว
plt.colorbar() # ใส่แถบสีทางขวา
plt.show()
ต่อมาขอลองยกตัวอย่างใหม่ คราวนี้ลองเพิ่มความละเอียดเป็นร้อยแล้วให้ค่าความสูงเป็นค่าที่ได้จากการคำนวณ โดยใช้เป็นฟังก์ชันพาราโบลาซึ่งมีค่าต่ำสุดที่จุด 0,0 ที่ใจกลาง
x,y = np.meshgrid(np.linspace(-4,4,100),np.linspace(-4,4,100))
z = x**2+y**2 # ค่าความสูงเป็นพาราโบลา
plt.axes(xlim=[-4,4],ylim=[-4,4]) # กำหนดขอบเขตให้อยู่ในช่วง -4 ถึง 4
plt.scatter(x,y,c=z,s=15,marker='s',cmap='jet',lw=0) # วางจุด
plt.colorbar()
plt.show()
การสร้างแผนภาพไล่สี แม้ว่า scatter จะใช้ทำแผนภาพไล่สีได้ แต่จริงๆแล้วมันไม่ได้ถูกออกแบบมาเพื่อการนี้โดยเฉพาะ มีอีกวิธีที่เหมาะกว่า นั่นคือใช้ฟังก์ชัน pcolor
ฟังก์ชันนี้มีไว้ระบายสีพื้นตามสีที่กำหนดในตำแหน่งต่างๆ การใช้ก็คล้ายๆกับ scatter ใส่อาร์กิวเมนต์สองตัวแรกเป็นแกน x และ y และตัวที่ ๓ ใส่ค่าสี
ลองแทนโค้ดตัวอย่างที่แล้ว บรรทัด plt.scatter ด้วย pcolor
plt.pcolor(x,y,z,cmap='jet')
ผลที่ได้จะแทบไม่ต่างจากเดิม
แต่ pcolor นั้นเมื่อใช้แล้วพื้นสีจะกระจายทั่วฉากเสมอ จะวางตำแหน่งจุดห่างแค่ไหนก็ได้
ถ้าวางจำนวนจุดน้อยก็จะเห็นเป็นเหมือนภาพที่ความละเอียดต่ำ
จะให้แนวตั้งและนอนมีความละเอียดไม่เท่ากันก็ได้ ตัวอย่างเช่น
x,y = np.meshgrid(np.linspace(-40,40,13),np.linspace(-40,40,50))