วันเดือนปีและเวลาถือเป็นปริมาณอย่างหนึ่งที่มีความสำคัญแต่ก็มีความยุ่งยากในการจัดการ
ไพธอนมีมอดูล datetime ซึ่งได้จัดเตรียมออบเจ็กต์ชนิด datetime.time, datetime.date และ datetime.datetime ซึ่งเก็บค่าวันเดือนปีและเวลาในหน่วยต่างๆอย่างเป็นระบบเพื่อให้จัดการง่าย
รายละเอียดได้เขียนเอาไว้แล้วใน
https://phyblas.hinaboshi.com/20160621 ใน matplotlib สามารถใช้ข้อมูลจากมอดูล datetime มาเป็นค่าสำหรับวาดกราฟได้ด้วย
เพื่อจะไม่เป็นการอธิบายซ้ำซ้อน เนื้อหาเกี่ยวกับ datetime ทั้งหมดจะอ้างอิงจากบทความเรื่อง datetime ดังนั้นก่อนที่จะอ่านเนื้อหาในบทนี้จำเป็นจะต้องอ่านเนื้อหาตรงนั้นก่อน เพราะใช้ออบเจ็กต์ datetime จากในนั้นเป็นหลัก
การใช้ datetime เป็นข้อมูลในกราฟ เมื่อมีการใช้ datetime มาเป็นค่าในกราฟ ตำแหน่งเส้นขีดและรูปแบบการแสดงผลจะถูกกำหนดขึ้นโดยอัตโนมัติตามความเหมาะสม
ตัวอย่าง ลองให้ข้อมูลแนวตั้งเป็นข้อมูลชนิด datetime และแกน x เป็นเลขสุ่มซึ่งมีค่าเปลี่ยนแปลงไปตามเวลา
import numpy as np
import matplotlib.pyplot as plt
import datetime
roem = datetime.datetime(2016,3,21,0,0,0)
x = np.random.uniform(-3,3,120).cumsum()
y = [roem+datetime.timedelta(0,2000*i) for i in range(0,120)]
plt.figure(figsize=[6,7])
ax = plt.axes([0.2,0.05,0.78,0.90])
plt.plot(x,y,'ro-')
plt.show()
จะเห็นว่าเส้นขีดแนวตั้งแสดงเวลาที่ห่างไปเป็นชั่วโมงไปเรื่อยๆแต่ไม่ได้แสดงวันเดือนปีที่ใส่
แต่ถ้าช่วงเวลาห่างกันมากเป็นวันก็จะได้เส้นขีดแบ่งเป็นวัน แล้วก็ไม่ได้แสดงเวลาให้เห็น
เช่นลองดูตัวอย่างนี้ คราวนี้ลองให้เวลาเป็นแกน x บ้าง จึงต้องปรับตัวเลขให้เอียงเล็กน้อยเพื่อไม่ให้ทับกัน
roem = datetime.datetime(2016,3,21,0,0,0)
x = [roem+datetime.timedelta(i) for i in range(0,120)]
y1 = np.random.uniform(-3,3,120).cumsum()
y2 = np.random.uniform(-1,1,120).cumsum()
plt.plot(x,y1,'ro-',mfc='#aa77cc',ms=4)
plt.plot(x,y2,'go-',mfc='#4477ee',ms=4)
plt.xticks(rotation=15)
plt.show()
หรือถ้าหากช่วงห่างกันมากนักก็จะแสดงแต่ปีอย่างเดียว
บางทีรูปแบบที่แสดงนี้อาจไม่เป็นไปตามที่ต้องการ เช่นอาจจะอยากให้แสดงทั้งวันที่และเวลาพร้อมกัน หรืออยากเปลี่ยนวันที่ให้เรียงตาม วัน-เดือน-ปี
แต่เราก็สามารถปรับ รูปแบบและตำแหน่งขีดเอาเองได้ตามที่ต้องการ โดยใช้เมธอดของ xaxis และ yaxis เช่นเดียวกับที่กล่าวถึงในบทที่แล้ว เพียงแต่ว่าในบทนี้ออบเจ็กต์ที่จะใช้จะมาจากอีกมอดูลย่อยหนึ่งใน matplotlib นั่นคือมอดูล matplotlib.dates ซึ่งเป็นมอดูลย่อยที่มีหน้าที่จัดการเกี่ยวกับเวลา
การเปลี่ยนรูปแบบการแสดงผลของวันเวลา ในการปรับรูปแบบการแสดงผลวันเวลาให้เป็นไปในแบบตามที่ต้องการมีฟังก์ชันที่ สะดวกอยู่คือ matplotlib.dates.DateFormatter ฟังก์ชันนี้จะกำหนดรูปแบบของวันเวลาที่แสดงออกมาโดยวิธีการแบบเดียวกับ ฟังก์ชัน datetime.strftime คือใช้ %d แทนวัน %m แทนเดือน %Y แทนปี เป็นต้น
การใช้งานคือสร้างต้องออบเจ็กต์ matplotlib.dates.DateFormatter โดยใส่อาร์กิวเมนต์เป็นสายอักขระที่ระบุรูปแบบการแสดงผลที่ต้องการ จากนั้นนำมาใช้ในเมธอด set_major_formatter หรือ set_minor_formatter
ตัวอย่าง แสดงในวันเดือนปีในรูปแบบ วัน-เดือน-ปี
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
import datetime
roem = datetime.datetime(2016,3,21,0,0,0)
x = np.random.uniform(-2,3,120).cumsum()
y = [roem+datetime.timedelta(i) for i in range(0,120)]
plt.figure(figsize=[6,7])
ax = plt.axes([0.18,0.05,0.79,0.92])
plt.plot(x,y,'o-',mfc='#bb66cc')
dfmt = mpl.dates.DateFormatter('%d-%m-%Y')
ax.yaxis.set_major_formatter(dfmt)
แต่ถ้าหากต้องการจะตั้งรูปแบบการแสดงผลให้อิสระกว่านั้นก็อาจเป็นการดีกว่าหากใช้ matplotlib.ticker.FuncFormatter ซึ่งแนะนำไปใน
บทที่แล้ว เพียงแต่ต้องระวังว่า ค่า datetime เวลาที่อยู่ในระบบของ matplotlib นั้นที่จริงแล้วจะถูกแปลงให้อยู่ในรูปของตัวเลขจำนวนจริงซึ่งเป็นค่าจำนวนวันซึ่งนับไล่ตั้งแต่วันที่ 1 ม.ค. ปี ค.ศ. 1
เราสามารถเปลี่ยนเป็นค่าตัวเลขนั้นได้โดยใช้ฟังก์ชัน matplotlib.dates.date2num เช่น
print(mpl.dates.date2num(datetime.datetime(1,1,1,0,0,0))) # ได้ 1.0
print(mpl.dates.date2num(datetime.datetime(2016,6,21,0,0,0))) # ได้ 736136.0
ในทางกลับกันตัวเลขนี้สามารถแปลงกลับเป็น datetime ได้โดยฟังก์ชัน matplotlib.dates.num2date เช่น
print(mpl.dates.num2date(1)) # ได้ 0001-01-01 00:00:00+00:00
print(mpl.dates.num2date(700000.1)) # ได้ 1917-07-15 02:24:00+00:00
หากลองวาดกราฟโดยใช้ datetime กับ matplotlib.ticker.FuncFormatter ค่าที่ถูกส่งไปเป็นตัวแปรตัวแรกจะถูกแปลงเป็นตัวเลขเหมือนกับใช้ matplotlib.dates.date2num
ตัวอย่าง
def fy(k,p):
return '%.3f'%k
roem = datetime.datetime(2016,6,21,0,0,0)
x = np.random.uniform(-1,3,120).cumsum()
y = [roem+datetime.timedelta(0,500*i) for i in range(0,120)]
plt.figure(figsize=[6,7])
ax = plt.axes([0.18,0.05,0.79,0.92])
plt.plot(x,y,'o-',mfc='#bb6622')
ax.yaxis.set_major_formatter(mpl.ticker.FuncFormatter(fy))
plt.show()
วันที่ 21 มิ.ย. 2016 มีค่าเท่ากับ 736136 ส่วนทศนิยมก็คือเวลาเป็นชั่วโมงในวันนั้น
ในเมื่อค่าที่ถูกส่งมาอยู่ในรูปแบบตัวเลขอย่างนี้ เราสามารถแปลงกลับเป็น datetime อีกทีได้ด้วยฟังก์ชัน matplotlib.dates.num2date จากนั้นก็นำไปใช้ทำอะไรได้ง่าย
ตัวอย่างเช่น ลองทำให้แสดงผลค่าชั่วโมงเป็นแบบไทยๆ
def mong(k,p):
c = mpl.dates.num2date(k).hour
if(c==0):
return u'เที่ยงคืน'
if(c>0 and c<6):
return u'ตี %d'%c
if(c>=6 and c<12):
return u'%d โมงเช้า'%c
if(c==12):
return u'เที่ยง'
if(c==13):
return u'บ่ายโมง'
if(c==14 or c==15):
return u'บ่าย %d โมง'%(c-12)
if(c>15 and c<=18):
return u'%d โมงเย็น'%(c-12)
if(c>18):
return u'%d ทุ่ม'%(c-18)
roem = datetime.datetime(2016,6,21,0,0,0)
y = [roem+datetime.timedelta(0,1400*i) for i in range(0,60)]
plt.figure(figsize=[6,7])
ax = plt.axes([0.18,0.05,0.79,0.92])
for i in range(15):
x = np.random.uniform(-2,3,60).cumsum()
plt.plot(x,y,'.-')
plt.yticks(fontname='Tahoma',fontsize=16)
ax.yaxis.set_major_formatter(mpl.ticker.FuncFormatter(mong))
plt.show()
การปรับตำแหน่งขีดของเวลา ในการกำหนดตำแหน่งขีดของเวลาให้เป็นไปตามที่ต้องการนั้นสามารถทำได้โดยใช้เมธอด set_major_locator หรือ set_minor_locator
สำหรับข้อมูลที่เป็นวันเวลาแล้วค่าที่เหมาะจะใส่ในเมธอดนี้ก็คือออบเจ็กต์ที่เป็น คลาสย่อยของคลาสที่ชื่อ matplotlib.dates.DateLocator ซึ่งเป็นคลาสของตัวกำหนดขีดที่เป็นเวลาในรูปแบบต่างๆหลายแบบ ซึ่งได้แก่
YearLocator
MonthLocator
WeekdayLocator
DayLocator
HourLocator
MinuteLocator
SecondLocator
MicrosecondLocator
YearLocator จะกำหนดให้วางขีดโดยนับเป็นปีๆไป โดยที่จำนวนปีต่อขีดกำหนดโดยอาร์กิวเมนต์ตัวแรก แล้วก็กำหนดวันที่ที่จะทำการขีดในแต่ละปีโดยคีย์เวิร์ด month และ day
ตัวอย่าง ลองให้ตั้งขีดทุกวันที่ 29 กุมภาพันธ์ของทุก ๔ ปี ซึ่งเป็นปีอธิกสุรทิน (ปีที่มี ๓๖๖ วัน)
roem = datetime.datetime(2016,6,21,0,0,0)
y = [roem-datetime.timedelta(400*i) for i in range(0,60)]
plt.figure(figsize=[6,7])
ax = plt.axes([0.18,0.05,0.79,0.92])
for i in range(30):
x = np.random.uniform(-1,1,60).cumsum()
plt.plot(x,y,'.-')
ax.yaxis.set_major_formatter(mpl.dates.DateFormatter('%d-%m-%Y'))
ax.yaxis.set_major_locator(mpl.dates.YearLocator(4,month=2,day=29))
plt.show()
MonthLocator จะเป็นการขีดทุกเดือนที่กำหนดในแต่ละปี โดยใส่เดือนที่กำหนดเป็นอาร์กิวเมนต์ตัวแรกจะกำหนดเดือนเดียวก็ได้หรือถ้าจะ กำหนดให้ขีดหลายเดือนในแต่ละปีก็ใส่ในรูปลิสต์
จากนั้นอาร์กิวเมนต์ตัวที่สองจะเป็นตัวกำหนดว่าจะให้ขีดวันที่เท่าไหร่ในเดือน จะกำหนดเป็นวันเดียวหรือหลายวันเป็นลิสต์ก็ได้
ตัวอย่างเช่น ถ้าต้องการให้ขีกทุกวันที่ 5 ของเดือนกรกฎาคมและธันวาคม
roem = datetime.datetime(2016,6,21,0,0,0)
y = [roem-datetime.timedelta(50*i) for i in range(0,60)]
plt.figure(figsize=[6,7])
ax = plt.axes([0.18,0.05,0.79,0.92])
for i in range(30):
x = np.random.uniform(-1,1,60).cumsum()
plt.plot(x,y,'.-')
ax.yaxis.set_major_formatter(mpl.dates.DateFormatter('%d-%m-%Y'))
ax.yaxis.set_major_locator(mpl.dates.MonthLocator([7,12],5))
plt.show()
นอกจากนี้ยังอาจกำหนดให้ขีดโดยมีระยะห่างเป็นจำนวนเดือนเท่าๆกันได้โดยการกำหนด เป็นอาร์กิวเมนต์ตัวที่สามแทน ส่วนอาร์กิวเมนต์ตัวแรกก็ปล่อยเป็น None
ตัวอย่าง ลองเอาตัวอย่างที่แล้วมาแก้บรรทัด set_major_locator ให้ขีดทุกวันที่ 10 ของทุก ๕ เดือน
ax.yaxis.set_major_locator(mpl.dates.MonthLocator(None,10,5))
กรณีนี้อาจเขียนในรูปของคีย์เวิร์ดแทนได้ โดยเลขวันใส่เป็น bymonthday= และจำนวนเดือนที่เว้นใส่เป็น interval= แบบนี้ก็จะไม่ต้องใส่ None ขึ้นไว้ข้างหน้า
กล่าวคือ แก้บรรทัด set_major_locator เป็น
ax.yaxis.set_major_locator(mpl.dates.MonthLocator(bymonthday=10,interval=5))
WeekdayLocator จะกำหนดให้ขีดทุกวันใดวันหนึ่งในสัปดาห์ อาร์กิวเมนต์ตัวแรกเป็นตัวกำหนดว่าจะขีดวันไหนในสัปดาห์ โดยวันจันทร์เป็น 0 อาทิตย์เป็น 6 จะใส่ตัวเดียวหรือใส่เป็นลิสต์ของตัวเลขก็ได้ ส่วนอาร์กิวเมนต์ตัวที่สองจะกำหนดว่าจะเว้นกี่สัปดาห์ต่อขีด
ตัวอย่าง กำหนดให้ขีดทุกวันอังคาร (เลข 1) โดยเว้นช่วง 2 สัปดาห์
roem = datetime.datetime(2016,6,21,0,0,0)
y = [roem-datetime.timedelta(3*i) for i in range(0,60)]
plt.figure(figsize=[6,7])
ax = plt.axes([0.18,0.05,0.79,0.92])
for i in range(30):
x = np.random.uniform(-1,1,60).cumsum()
plt.plot(x,y,'.-')
plt.yticks(fontsize=21)
ax.yaxis.set_major_formatter(mpl.dates.DateFormatter('%d/%m'))
ax.yaxis.set_major_locator(mpl.dates.WeekdayLocator(1,2))
plt.show()
สำหรับ DayLocator นั้นจะขีดทุกวันที่ที่กำหนดในแต่ละเดือน วันที่ต้องการขีดนั้นให้กำหนดเป็นลิสต์ ไม่จำเป็นต้องเว้นห่างเท่ากัน
ตัวอย่าง
roem = datetime.datetime(2016,6,21,0,0,0)
y = [roem-datetime.timedelta(i*2.5) for i in range(0,60)]
plt.figure(figsize=[6,7])
ax = plt.axes([0.18,0.05,0.79,0.92])
for i in range(30):
x = np.random.uniform(-1,1,60).cumsum()
plt.plot(x,y,'.-')
ax.yaxis.set_major_formatter(mpl.dates.DateFormatter('%d/%m/%Y'))
ax.yaxis.set_major_locator(mpl.dates.DayLocator([7,14,21],2))
plt.show()
นอกจากกำหนดเป็นรายการของเลขวันแล้วเราอาจกำหนดเป็นระยะเว้นเท่าๆกันโดยกำหนดจำนวน วันได้ ทำได้โดยใส่จำนวนวันที่จะเว้นช่วงไว้ในอาร์กิวเมนต์ตัวที่สอง ส่วนตัวแรกก็ปล่อย None ไป
เช่นลองแก้บรรทัด set_major_formatter เป็น
ax.yaxis.set_major_locator(mpl.dates.DayLocator(None,8))
แบบนี้ก็จะได้ขีดที่เว้นระยะห่างเท่าๆกันทีละ ๘ วัน
หรือจะใส่ในรูปคีย์เวิร์ดเป็น interval= โดยไม่ใส่อาร์กิวเมนต์ตัวแรกก็ได้
ax.yaxis.set_major_locator(mpl.dates.DayLocator(interval=8))
ส่วน HourLocator ก็ทำนองเดียวกับ DayLocator คือกำหนดเวลาชั่วโมงภายในวัน หรือจะกำหนดระยะห่างเป็นชั่วโมงก็ได้
ตัวอย่าง
roem = datetime.datetime(2016,6,21,0,0,0)
y = [roem+datetime.timedelta(0,i*8000) for i in range(0,60)]
plt.figure(figsize=[6,7])
ax = plt.axes([0.18,0.05,0.79,0.92])
for i in range(30):
x = np.random.uniform(-1,1,60).cumsum()
plt.plot(x,y,'.-')
ax.yaxis.set_major_formatter(mpl.dates.DateFormatter('%I:%M'))
ax.yaxis.set_major_locator(mpl.dates.HourLocator([1,6,11]))
plt.show()
MinuteLocator ก็เช่นกัน กำหนดนาทีภายในชั่วโมง หรือกำหนดระยะห่างเป็นนาที
ตัวอย่าง
def nalika(k,p):
c = mpl.dates.num2date(k)
return u'%d:%02d น.'%(c.hour,c.minute)
roem = datetime.datetime(2016,6,21,0,0,0)
y = [roem+datetime.timedelta(0,i*900) for i in range(0,60)]
plt.figure(figsize=[6,7])
ax = plt.axes([0.18,0.05,0.79,0.92])
for i in range(30):
x = np.random.uniform(-1,1,60).cumsum()
plt.plot(x,y,'.-')
plt.yticks(fontname='Tahoma',fontsize=16)
ax.yaxis.set_major_formatter(mpl.ticker.FuncFormatter(nalika))
ax.yaxis.set_major_locator(mpl.dates.MinuteLocator(None,50))
plt.show()
อ้างอิง