φυβλαςのβλογ
phyblas的博客



numpy & matplotlib เบื้องต้น บทที่ ๒๕: การปรับแต่งคัลเลอร์แม็ป
เขียนเมื่อ 2016/06/12 10:25
แก้ไขล่าสุด 2021/09/28 16:42
ในบทที่แล้วได้แนะนำสิ่งที่เรียกว่าคัลเลอร์แม็ปและการใช้งานเพื่อสร้างแผนภาพไล่สีไปแล้ว

ในบทนี้จะเป็นเนื้อเพิ่มเติมเสริมจากบทที่แล้ว โดยจะเพิ่มทางเลือกในการปรับแต่งอะไรต่างๆให้มากขึ้น



การปรับขอบเขตของแถบสี
ตาม ที่ได้กล่าวไปในบทที่แล้วว่าฟังก์ชันคัลเลอร์แม็ปนั้นจะรับค่าตัวเลขในช่วง 0 ถึง 1 มาแล้วแปลงเป็นค่าแม่สีทั้ง ๓ (และยังมีตัวที่ ๔ คือค่าความโปร่งใสซึ่งในที่นี้จะไม่พูดถึง)

เช่นลองดูคัลเลอร์แม็ป ที่ชื่อ rainbow ซึ่งไล่สีเป็นสีรุ้ง ลองทดสอบค่าสีโดยการใส่ค่าตั้งแต่ 0 ถึง 1 ลงในฟังก์ชัน get_cmap('rainbow')()
import matplotlib.pyplot as plt
for c in np.arange(0,1.1,0.1):
    print('%s: %s'%(c,plt.get_cmap('rainbow')(c)))

ผลลัพธ์
0.0: (0.5, 0.0, 1.0, 1.0)
0.1: (0.30392156862745101, 0.30315267411304353, 0.98816547208125938, 1.0)
0.2: (0.099999999999999978, 0.58778525229247314, 0.95105651629515353, 1.0)
0.3: (0.096078431372549011, 0.80538091938883261, 0.8924005832479478, 1.0)
0.4: (0.30000000000000004, 0.95105651629515353, 0.80901699437494745, 1.0)
0.5: (0.50392156862745097, 0.99998102734872685, 0.70492554690614728, 1.0)
0.6: (0.69999999999999996, 0.95105651629515364, 0.58778525229247325, 1.0)
0.7: (0.90392156862745088, 0.80538091938883272, 0.45124405704532283, 1.0)
0.8: (1.0, 0.58778525229247325, 0.30901699437494745, 1.0)
0.9: (1.0, 0.30315267411304359, 0.15339165487868542, 1.0)
1.0: (1.0, 1.2246467991473532e-16, 6.123233995736766e-17, 1.0)

จะเห็นว่าค่าสีที่ 0 คือสีม่วง และที่ 1 คือสีแดง

แม้ว่าคัลเลอร์แม็ปจะมีค่าเปลี่ยนแปลงอยู่แค่ในช่วง 0 ถึง 1 แต่เวลาที่เราใช้งานคัลเลอร์แม็ปจริงๆนั้นเราไม่จำเป็นต้องมาคอยเปลี่ยน ค่าที่จะใช้เป็นค่าสีให้อยู่ในช่วง 0 ถึง 1 นั่นเป็นเพราะโดยปกติเวลาที่นำคัลเลอร์แม็ปมาใช้งานเช่นใส่ลงในแผนภาพไล่สี สีจะถูกไล่จากค่าต่ำสุดไปยังค่าสูงสุด

เช่นลองดูตัวอย่างนี้
import numpy as np
import matplotlib.pyplot as plt
x,y = np.meshgrid(np.linspace(-10,10,101),np.linspace(-10,10,101))
z = np.sin(x)+np.sin(y)*4 # ค่าสี
plt.pcolor(x,y,z,cmap='rainbow')
plt.colorbar()
plt.show()



ภาพนี้มีการไล่สีตั้งแต่ค่าต่ำสุดคือ -5 เป็นสีม่วง และค่าสูงสุดคือ 5 เป็นสีแดง กรณีนี้โปรแกรมได้ทำการแปลง -5 เป็น 0 และ 5 เป็น 1 และค่าระหว่างนั้นก็เทียบสัดส่วนกันไป การทำแบบนี้เรียกว่านอร์มาไลซ์ (normalize)

การที่สีไล่จากสูงไปจนต่ำอย่างสวยงามโดยที่เราไม่ต้องมา กำหนดค่าสูงสุดและต่ำสุดเอง แบบนี้ก็เป็นอะไรที่สะดวกดี อย่างไรก็ตามบางครั้งก็ก่อให้เกิดปัญหาอันไม่พึงประสงค์ได้เช่นกัน เช่นสมมุติว่ามีจุดประหลาดอันเดียวที่ข้อมูลห่างจากอันอื่นมันจะดึงให้ส่วน ที่เหลือไล่สีอยู่ภายในขอบเขตแคบๆทันที

ตัวอย่างเช่น
x,y = np.meshgrid(np.linspace(-10,10,101),np.linspace(-10,10,101))
z = np.sin(x)+np.sin(y)*4
z[50,50] = 30 # เพิ่มบรรทัดนี้เข้ามา
plt.pcolor(x,y,z,cmap='rainbow')
plt.colorbar()
plt.show()



จะเห็นว่ามีจุดแดงโดดขึ้นมาเพียงจุดเดียวตรงกลาง ที่เหลือเป็นสีออกม่วงหมดเลย

ดังนั้นบางครั้งจึงอาจเป็นการดีถ้าเราจะกำหนดค่าสูงสุดและต่ำสุดตามที่ต้องการได้

เราสามารถใส่ค่า vmin และ vmax เพื่อกำหนดขอบเขตค่าต่ำสุดและสูงสุดได้ เช่น ลองแก้บรรทัด plt.pcolor เป็น
plt.pcolor(x,y,z,cmap='rainbow',vmin=-3,vmax=3)



จะเห็นว่าคราวนี้สีจะมีการไล่เฉพาะในช่วงระหว่าง vmin ถึง vmax เท่านั้น ถ้าต่ำกว่า vmin หรือสูงกว่า vmax จะถือว่าเป็นสีเดียวกันหมด ไม่ว่าจะสูงกว่าหรือต่ำกว่าไปเยอะแค่ไหนก็ตาม

พอกำหนดขอบเขตตายตัวแบบนี้แล้วก็ทำให้กำจัดปัญหาการมีจุดที่มีค่าโดดขึ้นแบบผิดปกติได้ทันที

เมื่อกำหนดขอบเขต vmin และ vmax ไว้จะทำให้การนอร์มาไลซ์ทำตามช่วงขอบเขตนั้น หากเขียนเป็นสูตรคำนวณก็อาจเขียนเป็น
c = (z-vmin)/(vmax-vmin)



ออบเจ็กต์สำหรับนอร์มาไลซ์
ใน matplotlib นั้นได้จัดเตรียมออบเจ็กชนิดหนึ่งที่เอาไว้กำหนดค่าในการนอร์มาไลซ์ เราสามารถจะนำมันมาใช้เพื่อกำหนดรูปแบบการแสดงผลของแผนภาพไล่สีได้

ออบเจ็กต์ชนิดนั้นอยู่ในมอดูลย่อยหนึ่งของ matplotlib ชื่อ colors

ขอเริ่มด้วยการยกตัวอย่างการใช้แล้วจึงอธิบายทีหลัง

ตัวอย่าง พิมพ์ตามนี้จะได้ผลเหมือนกับตัวอย่างที่แล้ว
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl

cnorm = mpl.colors.Normalize(vmin=-3,vmax=3)
x,y = np.meshgrid(np.linspace(-10,10,101),np.linspace(-10,10,101))
z = np.sin(x)+np.sin(y)*4
z[50,50] = 30
plt.pcolor(x,y,z,cmap='rainbow',norm=cnorm)
plt.colorbar()
plt.show()

ข้อแตกต่างจากตัวอย่างที่แล้วก็คือแทนที่จะกำหนด vmin=-3,vmax=3 ตรงฟังก์ชัน plt.pcolor โดยตรงก็ไปสร้างออบเจ็กต์ mpl.colors.Normalize ขึ้น
cnorm = mpl.colors.Normalize(vmin=-3,vmax=3)

จากนั้นจึงนำออบเจ็กต์ cnorm นี้มาใส่เป็นคีย์เวิร์ด norm ในฟังก์ชัน plt.pcolor อีกที
plt.pcolor(x,y,z,cmap='rainbow',norm=cnorm)

cnorm ที่สร้างขึ้นมานั้นที่จริงแล้วก็คือเป็นฟังก์ชันที่ทำการแปลงตัวเลขจาก -3 ถึง 3 ให้เป็น 0 ถึง 1 นั่นเอง ลองดู
for c in np.linspace(-3,3,11):
    print('cnorm(%s) = %s'%(c,cnorm(c)))

จะได้
cnorm(-3.0) = 0.0
cnorm(-2.4) = 0.1
cnorm(-1.8) = 0.2
cnorm(-1.2) = 0.3
cnorm(-0.6) = 0.4
cnorm(0.0) = 0.5
cnorm(0.6) = 0.6
cnorm(1.2) = 0.7
cnorm(1.8) = 0.8
cnorm(2.4) = 0.9
cnorm(3.0) = 1.0

ที่จริงแล้วหากแค่ต้องการปรับขอบเขตการไล่สีเฉยๆก็แค่ใส่คีย์เวิร์ด vmin และ vmax ลงใน plt.pcolor ตามเดิมก็ได้ ไม่ต่างกัน

อย่างไรก็ตามออบเจ็กต์สำหรับนอร์มาไลซ์นั้นไม่ได้มีเพียงแค่ตัวนอร์มาไลซ์รูปแบบเชิงเส้นง่ายๆอย่างที่เห็นนี้เสมอไป แต่ยังมีตัวนอร์มาไลซ์ในรูปแบบอื่นๆด้วย

ในที่นี้จะยกตัวอย่างที่น่าจะมีโอกาสได้ใช้บ่อยมาส่วนหนึ่ง



การไล่สีแบบเรขาคณิต
บางครั้งการไล่สีแบบเป็นเชิงเส้นก็อาจไม่ทำให้เราเห็นภาพอะไรบางอย่างชัดเจนมากพอ

ตัวอย่างเช่นกราฟที่ค่าสีเปลี่ยนค่าไปตามเลขยกกำลัง
x,y = np.meshgrid(np.linspace(-10,10,101),np.linspace(-10,10,101))
z = 2**x+2**y
plt.pcolor(x,y,z,cmap='rainbow')
plt.colorbar()
plt.show()



แบบนี้จะเห็นว่าสีมาเปลี่ยนตรงแถวขอบอย่างรวดเร็ว แต่หากปรับการไล่สีให้ไล่แบบเรขาคณิตแทนก็จะแสดงผลได้ดีขึ้น ซึ่งเราสามารถทำได้โดยการใช้ตัวนอร์มาไลซ์แบบเรขาคณิต

ลองดูตัวอย่างการใช้
x,y = np.meshgrid(np.linspace(-10,10,101),np.linspace(-10,10,101))
z = 2**x+2**y
clognorm = mpl.colors.LogNorm()
plt.pcolor(x,y,z,cmap='rainbow',norm=clognorm)
plt.colorbar()
plt.show()



เท่านี้ก็จะเห็นว่าการไล่สีกลายเป็นแบบเรขาคณิต แถบสีทางขวาก็กลายเป็นเลขยกกำลังฐานสิบด้วย

ในที่นี้ส่วนที่เปลี่ยนแปลงไปมีแค่เราเพิ่ม mpl.colors.LogNorm() เข้ามาเป็นคีย์เวิร์ดใน plt.pcolor

mpl.colors.LogNorm นี้เป็นออบเจ็กต์นอร์มาไลซ์แบบไล่ตามยกกำลังสิบ

สามารถใส่อาร์กิวเมนต์ vmin vmax เพื่อกำหนดขอบเขตได้เช่นเดียวกับ mpl.colors.Normalize

ลองแก้ clognorm เป็น
clognorm = mpl.colors.LogNorm(vmin=0.1,vmax=10)



ก็จะได้การไล่สีแค่ในช่วง 0.1 ถึง 10



ไล่สีแบบเป็นช่วงๆไม่ต่อเนื่อง
อีกรูปแบบหนึ่งที่อาจได้ใช้บ่อยก็คือการไล่สีแบบแบ่งเป็นลำดับขั้นไม่ต่อเนื่อง เป็นฟังก์ชันขั้นบันได

เราสามารถสร้างฟังก์ชันขั้นบันไดได้ด้วย mpl.colors.BoundaryNorm

ตัวอย่าง
x = np.linspace(0,2,101)
y = x**2
b = [0.,0.4,0.8,1.2,1.6,2.,2.4,2.8,3.2,3.6,4.]
cbn = mpl.colors.BoundaryNorm(b,40)
print(cbn(y))

ผลลัพธ์
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 4 4
 4 4 4 4 4 4 4 4 8 8 8 8 8 8 8 8 8 8 13 13 13 13 13 13 13 13 13 17 17 17 17
 17 17 17 21 21 21 21 21 21 21 26 26 26 26 26 26 30 30 30 30 30 30 34 34 34
 34 34 39 39 39 39 39 40]

ลองเอามาวาดกราฟเทียบระหว่างฟังก์ชัน y เดิมกับ y ที่ถูกแปลงค่าเป็นขั้นบันได
plt.subplot(2,1,1)
plt.plot(x,y)
plt.subplot(2,1,2)
plt.plot(x,cbn(y))
plt.show()



จะเห็นว่าฟังก์ชัน y ถูกแบ่งค่าออกเป็นช่วงๆตามค่า ค่ายิ่งมากก็ย่ิงอยู่ในขั้นที่สูง

จากที่เดิมเป็นค่าที่เพิ่มขึ้นเรื่อยๆอย่างต่อเนื่อง กลายมาเป็นว่าค่าเพิ่มขึ้นเป็นขั้นๆไม่ต่อเนื่อง ถ้าหากนี่เป็นค่าที่แทนสีก็จะได้ว่าสีเห็นสีเปลี่ยนไปเป็นช่วงๆ

กลับมาดูที่เลขที่ใส่ลงในฟังก์ชัน mpl.colors.BoundaryNorm อาร์กิวเมนต์ตัวแรกคือลิสต์หรืออาเรย์ของจุดแบ่งขีดที่ต้องการ ในที่นี้แบ่งทีละ 0.4 ไปเรื่อยๆจนถึงค่าสูงสุดคือ 4 รวมแล้วแบ่งเป็น 10 ช่วง (แต่ในลิสต์ต้องมี 11 ค่า)

ส่วนอาร์กิวเมนต์ตัวหลังคือค่าตัวเลขสูงสุด ค่าเป็นจำนวนเต็มเท่านั้น ค่าผลลัพธ์ที่ได้ออกมานั้นจะไล่ตั้งแต่ 0 ไปจนถึงตัวเลขที่กำหนดนี้ ในที่นี้กำหนดเป็น 40 ดังนั้นจึงไล่ตั้งแต่ 0 ถึง 40

โดยทั่วไปเมื่อนำมาใช้จริงจะกำหนดค่าเป็น 256 เพื่อจะนำมาใช้เป็นค่าใส่ในคัลเลอร์แม็ป

ที่จริงคัลเลอร์แม็ปนั้นนอกจากจะนอร์มาไลซ์เป็น 0 ถึง 1 แล้ว หาค่าที่ใส่ลงไปเป็นจำนวนเต็มจะเป็นการไล่ตั้งแต่ 0 ถึง 255 แทน
print(plt.get_cmap('rainbow')(0)) # ได้ (0.5, 0.0, 1.0, 1.0)
print(plt.get_cmap('rainbow')(1)) # ได้ (0.49215686274509807, 0.012319659535238442, 0.99998102734872685, 1.0)
print(plt.get_cmap('rainbow')(32)) # ได้ (0.24901960784313726, 0.38410574917192586, 0.98063477046897773, 1.0)
print(plt.get_cmap('rainbow')(224)) # ได้ (1.0, 0.37270199199091436, 0.18980109344182594, 1.0)
print(plt.get_cmap('rainbow')(255)) # ได้ (1.0, 1.2246467991473532e-16, 6.123233995736766e-17, 1.0)
print(plt.get_cmap('rainbow')(256)) # ได้ (1.0, 1.2246467991473532e-16, 6.123233995736766e-17, 1.0)
print(plt.get_cmap('rainbow')(1.)) # ได้ (1.0, 1.2246467991473532e-16, 6.123233995736766e-17, 1.0)

จะเห็นว่า plt.get_cmap('rainbow')(1) กับ plt.get_cmap('rainbow')(1.) ได้ค่าไม่เท่ากัน แต่ plt.get_cmap('rainbow')(1.) กลับเท่ากับ plt.get_cmap('rainbow')(255) แทน เพราะการใส่ค่าเป็นเลขจำนวนจริงกับจำนวนเต็มนั้นคิดต่างกัน ต้องระวังตรงนี้ด้วย

เมื่อเข้าใจแล้วคราวนี้ก็มาลองใช้ mpl.colors.BoundaryNorm กับแผนภาพไล่สีกันดูเลย ลองให้วาดภาพขึ้นมาหลายภาพ ภาพแรกเป็นการไล่สีแบบต่อเนื่อง ส่วนสามภาพที่เหลือให้วาดภาพโดยแบ่งสีเป็นขั้นๆ โดยมีจำนวนขั้นต่างออกไปเป็น 20, 10 และ 5 ตามลำดับ
x,y = np.meshgrid(np.linspace(-10,10,101),np.linspace(-10,10,101))
z = np.cos(x/5)**2+np.cos(y/5)**2
# แผนภาพไล่สีต่อเนื่อง
plt.figure()
plt.pcolor(x,y,z,cmap='rainbow')
plt.colorbar()
# แบ่งเป็น 20 ช่วง
plt.figure()
cbn = mpl.colors.BoundaryNorm(np.linspace(0,2,21),256)
plt.pcolor(x,y,z,cmap='rainbow',norm=cbn)
plt.colorbar()
# แบ่งเป็น 10 ช่วง
plt.figure()
cbn = mpl.colors.BoundaryNorm(np.linspace(0,2,11),256)
plt.pcolor(x,y,z,cmap='rainbow',norm=cbn)
plt.colorbar()
# แบ่งเป็น 5 ช่วง
plt.figure()
cbn = mpl.colors.BoundaryNorm(np.linspace(0,2,6),256)
plt.pcolor(x,y,z,cmap='rainbow',norm=cbn)
plt.colorbar()
plt.show()






แผนภาพไล่สีที่มีการแบ่งสีเป็นขั้นๆแบบนี้เรียกว่าคอนทัวร์ (contour) บางกรณีก็ช่วยให้เห็นภาพอะไรได้ชัดขึ้นกว่าการไล่สีต่อเนื่อง

อย่างไรก็ตามที่จริงนอกจากวิธีนี้แล้ว matplotlib ก็มีฟังก์ชันสำหรับสร้างคอนทัวร์โดยเฉพาะและดีกว่า ซึ่งจะกล่าวถึงในบทที่ ๒๘ ต่อไป



นอกจากรูปแบบการนอร์มาไลซ์ที่กล่าวมาข้างต้นแล้วนั้น ความจริงแล้วเรายังสามารถสร้างรูปแบบเฉพาะขึ้นมาเองได้อีกด้วย



อ้างอิง


<< บทที่แล้ว     บทถัดไป >>
หน้าสารบัญ


-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python >> matplotlib

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

目录

从日本来的名言
模块
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
机器学习
-- 神经网络
javascript
蒙古语
语言学
maya
概率论
与日本相关的日记
与中国相关的日记
-- 与北京相关的日记
-- 与香港相关的日记
-- 与澳门相关的日记
与台湾相关的日记
与北欧相关的日记
与其他国家相关的日记
qiita
其他日志

按类别分日志



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  查看日志

  推荐日志

ตัวอักษรกรีกและเปรียบเทียบการใช้งานในภาษากรีกโบราณและกรีกสมัยใหม่
ที่มาของอักษรไทยและความเกี่ยวพันกับอักษรอื่นๆในตระกูลอักษรพราหมี
การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
การใช้ unix shell เบื้องต้น ใน linux และ mac
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ