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



numpy & matplotlib เบื้องต้น บทที่ ๓๘: การใช้วันและเวลาเป็นค่าในกราฟ
เขียนเมื่อ 2016/06/26 00:44
แก้ไขล่าสุด 2021/09/28 16:42
วันเดือนปีและเวลาถือเป็นปริมาณอย่างหนึ่งที่มีความสำคัญแต่ก็มีความยุ่งยากในการจัดการ

ไพธอนมีมอดูล 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()





อ้างอิง


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


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

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

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

หมวดหมู่

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

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

目录

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

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

按类别分日志



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

  查看日志

  推荐日志

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