ניתוח והצגת סדרות נתונים מבוססות זמן באמצעות pandas
בסדרה מבוססת זמן (time series) ערכים מספריים מסודרים לפי זמנים. לדוגמה, מדידות הטמפרטורה מדי שעה או מחיר הסגירה היומי של מנייה בבורסה. Pandas הוא כלי מצוין להצגה ולניתוח סדרות מבוססות זמן. במדריך זה נדגים את היכולת הזו באמצעות ניתוח המחיר של מניות גוגל ואמזון לאורך שנים.
המדריך מבוסס על שני מדריכים קודמים בסדרה שמומלץ לקרוא אותם (אם כי לא חייבים כי המדריך עומד בפני עצמו):
- 18 פעולות שאתה צריך להכיר כשאתה עובד עם Pandas של Python
- 12 דברים שאתה חייב לדעת כשאתה מייצר תרשימים באמצעותmatplotlib של python
סביבת העבודה
כדי לתרגל את הקוד במדריך נייבא 4 ספריות:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import pandas_datareader as dr
- Numpy - בשביל עבודה עם סדרות של נתונים
- Pandas - לסידור, סינון והצגת מידע טבלאי בדומה לאקסל אבל הרבה יותר משוכלל
- Matplotlib - להצגת מידע בתרשימים
- Pandas_datareader - כדי לייבא את נתוני מחירי המניות ההיסטוריים.
מבני נתונים מבוססי תאריך והמרה לחותמת זמן תקנית
לפני שנצלול לתוך סט הנתונים נציג את מבני הנתונים המשמשים את pandas לעבודה עם תאריכים. ב-Pandas נקודת זמן בודדת מכונה חותמת זמן time stamp . בעיה עיקרית היא שלאומים שונים ואנשים שונים נוהגים להציג את הזמן באופנים שונים. לדוגמה, את התאריך 14.11.218 ניתן לייצג באופנים הבאים:
- 2018-11-14
- November 14, 2018
- 14/11/2018
- 2018.11.14
- 20181114
המתודה to_datetime מאפשרת לנו להמיר את הפורמט שאיתו אנו עובדים ל time stamp בפורמט אחיד.
בואו נראה כיצד המתודה ממירה תאריך אחד לחותמת זמן תקנית:
print(pd.to_datetime('14/11/2018'))
התוצאה:
Timestamp('2018-11-14 00:00:00')
מסקנה: כאשר משתמשים ב-to_datetime על נתון בודד מקבלים חזרה חותמת זמן.
כמעט תמיד כשאנחנו עובדים pandas אנחנו מעוניינים בסדרות של נתונים, סדרות של תאריכים. כאשר אנחנו עובדים עם סדרות המתודה to_datetime מחזירה אובייקט DatetimeIndex. נדגים את זה על ידי הפעלת המתודה על סדרה של תאריכים שכל אחד מהם בפורמט שונה:
# cast the dates into a datetime object
dates = ['2018-11-14', 'November 14, 2018', '14/11/2018', '2018.11.14', '20181114']
pd.to_datetime(dates)
התוצאה סדרת נתונים מבוססת DatetimeIndex
DatetimeIndex(['2018-11-14', '2018-11-14', '2018-11-14', '2018-11-14', '2018-11-14'], dtype='datetime64[ns]', freq=None)
לבסוף, Pandas יכול להתמודד ללא בעיה עם נתונים הכוללים שעה נוסף לתאריך:
# the same method can be applied for date time formats
dates = ['2018-11-14 1:08:24 PM', 'November 14, 2018 01:08:24 PM', '14/11/2018 13:08:24', '2018.11.14', '20181114']
pd.to_datetime(dates)
DatetimeIndex(['2018-11-14 13:08:24', '2018-11-14 13:08:24', '2018-11-14 13:08:24', '2018-11-14 00:00:00', '2018-11-14 00:00:00'], dtype='datetime64[ns]', freq=None)
טעינת מסד הנתונים
את מחירי המניות עבור גוגל (GOOG) ואמזון (AMZN) בין השנים 2015 ו-2018 נטען מ-Yahoo finance באמצעות pandas_datareader:
start = '2015-01-01'
end = '2018-12-31'
goog = dr.data.get_data_yahoo('GOOG', start=start, end=end)
amzn = dr.data.get_data_yahoo('AMZN', start=start, end=end)
בואו נציץ במסד הנתונים data frame שייבאנו. המתודה head משמשת לקריאת השורות הראשונות ו-tail לקריאת השורות האחרונות.
goog.head()
Date | High | Low | Open | Close | Volume | Adj Close |
---|---|---|---|---|---|---|
2017-01-03 | 789.630005 | 775.799988 | 778.809998 | 786.140015 | 1657300 | 786.140015 |
2017-01-04 | 791.340027 | 783.159973 | 788.359985 | 786.900024 | 1073000 | 786.900024 |
2017-01-05 | 794.479980 | 785.020020 | 786.080017 | 794.020020 | 1335200 | 794.020020 |
2017-01-06 | 807.900024 | 792.203979 | 795.260010 | 806.150024 | 1640200 | 806.150024 |
2017-01-09 | 809.966003 | 802.830017 | 806.400024 | 806.650024 | 1274600 | 806.650024 |
goog.tail()
Date | High | Low | Open | Close | Volume | Adj Close |
---|---|---|---|---|---|---|
2018-12-24 | 1003.539978 | 970.109985 | 973.900024 | 976.219971 | 1590300 | 976.219971 |
2018-12-26 | 1040.000000 | 983.000000 | 989.010010 | 1039.459961 | 2373300 | 1039.459961 |
2018-12-27 | 1043.890015 | 997.000000 | 1017.150024 | 1043.880005 | 2109800 | 1043.880005 |
2018-12-28 | 1055.560059 | 1033.099976 | 1049.619995 | 1037.079956 | 1414800 | 1037.079956 |
2018-12-31 | 1052.699951 | 1023.590027 | 1050.959961 | 1035.609985 | 1493300 | 1035.609985 |
כמה רשומות (ימים שונים) מתועדים במסד הנתונים?
print(goog.shape)
(502, 6)
502 רשומות ב-6 עמודות.
שאלה נוספת חשובה ביותר היא "מה האינדקס?" כי הוא יכול להיות מספרי או מבוסס תאריך, וזה עושה הבדל גדול באופן שבו אנחנו יכולים לגשת לנתונים.
print(goog.index)
DatetimeIndex(['2017-01-03', '2017-01-04', '2017-01-05', '2017-01-06', '2017-01-09', '2017-01-10', '2017-01-11', '2017-01-12', '2017-01-13', '2017-01-17', ... '2018-12-17', '2018-12-18', '2018-12-19', '2018-12-20', '2018-12-21', '2018-12-24', '2018-12-26', '2018-12-27', '2018-12-28', '2018-12-31'], dtype='datetime64[ns]', name='Date', length=502, freq=None)
שני דברים חשובים לנו במסד הנתונים:
- האינדקס של מסד הנתונים מבוסס תאריך, מה שפותח לנו אפשרויות רבות כשאנחנו רוצים לגשת לרשומות.
- העמודה Close שבה המחיר הסופי הלא מתואם של המניה מפני שזו העמודה היחידה בה נשתמש בהמשך המדריך.
קל מאוד להציג את השתנות המחיר לאורך זמן באמצעות המתודה plot שמציגה תרשים:
goog['Close'].plot()
מה לעשות עם מסד נתונים שבו האינדקס מספרי
לא תמיד יפנקו אותנו כל כך ויתנו לנו מסד נתונים שבו האינדקס מבוסס על תאריכים ואז נקבל לרוב אינדקס מבוסס מספרים. בחלק זה של המדריך נלמד כיצד להתמודד עם המצב שבו קיבלנו מסד נתונים שבו האינדקס מספרי ולא מבוסס תאריך.
כדי ללמוד, נשנה את האינדקס של מסד הנתונים לאינדקס מספרי.
goog.reset_index(inplace=True)
בוא נראה את האינדקס אחרי השינוי:
print(goog.index)
RangeIndex(start=0, stop=502, step=1)
האינדקס עכשיו מבוסס מספר ולא תאריך.
נציץ במסד הנתונים:
goog.head(2)
Date | High | Low | Open | Close | Volume | Adj Close | |
---|---|---|---|---|---|---|---|
0 | 2017-01-03 | 789.630005 | 775.799988 | 778.809998 | 786.140015 | 1657300 | 786.140015 |
1 | 2017-01-04 | 791.340027 | 783.159973 | 788.359985 | 786.900024 | 1073000 | 786.900024 |
זה רק מאשר את מה שאנחנו יודעים שעכשיו האינדקס הוא מספרי ונוספה עמודת Date עם התאריכים. זה מצב שהוא פחות רצוי כי אנחנו רוצים אינדקס מבוסס תאריכים אז בואו נראה כיצד לשנות את האינדקס ממספרי למבוסס תאריך בשני צעדים.
א. נמיר את עמודת התאריכים לפורמט תקני של חותמת זמן:
# cast to date
goog.Date = pd.to_datetime(goog.Date)
ב. נהפוך את עמודת התאריך לאינדקס הראשי:
# set the index to Date
goog.set_index('Date', inplace=True)
נוודא את מה שעשינו בשני אופנים:
print(goog.index)
print(goog.head())
הצגה של מסד הנתונים
הודות לאינדקס מבוסס חותמת הזמן אנחנו יכולים לפלח את מסד הנתונים בכל מיני דרכים.
מה המחיר הסוגר של המנייה בתאריך מסוים?
# We can get the value of a single data
# point based on time and column
goog.loc['2017-09-05','Close']
מה המחיר הסוגר בכל ימי המסחר לאורך כל שנת 2017?
# yearly by using the date index
goog['2017']['Close']
Date 2017-01-03 786.140015 2017-01-04 786.900024 2017-01-05 794.020020 ... 2017-12-27 1049.369995 2017-12-28 1048.140015 2017-12-29 1046.400024 Name: Close, Length: 251, dtype: float64
יותר מדי אינפורמציה. אולי תרשים פשוט יעזור?
goog['2017']['Close'].plot()
מה לגבי המחירים בחודש ספציפי?
# monthly
goog['2017-10']['Close'].plot()
והכי נחמד שאפשר לראות את הנתונים עבור טווח של תאריכים.
# within a range
goog['2017-09-05':'2017-10-25']['Close'].plot()
שינוי התדירות של סדרות מבוססות זמן
מסד הנתונים שלנו מבוסס על נתונים יומיים אבל הרבה פעמים אנחנו נרצה לשנות את תדירות ההצגה. לדוגמה, להציג מחירים על בסיס תדירות שבועית, חודשית, רבעונית. אז גם לזה יש ל-Pandas פתרון באמצעות הפונקציה resample.
לדוגמה, אנחנו רוצים את המחיר הממוצע של המנייה על בסיס חודשי בשנת 2017, ולשם כך נעביר לפונקציה resample את הפרמטר M (קיצור של Month):
# The average prices on a monthly frequency
goog.Close['2017'].resample('M').mean()
Date 2017-01-31 807.904752 2017-02-28 816.916581 2017-03-31 834.111307 2017-04-30 844.056840 2017-05-31 939.284085 2017-06-30 953.766824 2017-07-31 942.865009 2017-08-31 922.023045 2017-09-30 931.299503 2017-10-31 982.522275 2017-11-30 1030.492844 2017-12-31 1043.653003 Freq: M, Name: Close, dtype: float64
שימו לב! כדי לעבוד עם resample צריך לשרשר פונקצית אגרגציה, דוגמת mean בשביל הממוצע או std בשביל סטיית התקן.
גם במקרה זה תרשים יכול לעזור.
goog.Close['2017'].resample('M').mean().plot()
מה בנוגע לממוצע הריבעוני של המחירים על פני התקופה כולה?
# quarterly
goog.Close.resample('Q').mean().plot()
ישנם פרמטרים נוספים שניתן להעביר. תוכלו למצוא אותם בתיעוד הרשמי של הספרייה : Pandas time series offset aliases
השוואה בין מחירי מניות
כדי ליצור מסד נתונים שמשווה בין מחירי המניות של אמזון וגוגל נעשה את הצעדים הבאים:
- ניצור שני dataframe נפרדים עבור המחיר של כל מנייה מהעמודה Close.
- נשנה את שם העמודה לפי שם המנייה כדי שנוכל לדעת לאיזו מניה שייכים הנתונים.
- נחבר את שני ה-dataframes למסד נתונים יחיד.
# get only the 'Close' column
goog_price = goog[['Close']]
amzn_price = amzn[['Close']]
# rename the columns
goog_price = goog_price.rename({'Close':'GOOGLE'}, axis='columns')
amzn_price = amzn_price.rename({'Close':'AMAZON'}, axis='columns')
# combine the stocks horizontally
stocks = pd.concat([goog_price, amzn_price], axis=1)
נוודא שאכן קיבלנו את מבנה מסד הנתונים הרצוי:
stocks.head()
Date | AMAZON | |
---|---|---|
2017-01-03 | 786.140015 | 753.669983 |
2017-01-04 | 786.900024 | 757.179993 |
2017-01-05 | 794.020020 | 780.450012 |
2017-01-06 | 806.150024 | 795.989990 |
2017-01-09 | 806.650024 | 796.919983 |
התרשים הבא מציג את ההשוואה בין המניות על בסיס שנתי:
stocks['2017'].plot()
חלון מתגלגל
חלון מתגלגל (rolling window) היא טכניקה חשובה לניתוח של נתונים מבוססי זמן כי היא "מחליקה" את תצוגת הנתונים ובכך מקלה עלינו לראות מגמות ארוכות טווח.
המתודה rolling היא הפונקציה של Pandas שיוצרת את החלון המתגלגל. תמיד מוסיפים לה פונקצית אגרגציה (mean, std, median, sum). לדוגמה, חלון מתגלגל על פני 21 נקודות זמן.
stocks.rolling(21).mean().plot()
נשווה לחלון מתגלגל המבוסס על חמישה ימים.
stocks.rolling(5).mean().plot()
אפשר לראות שהנתונים על פני 21 יום הם חלקים יותר.
סיכום
במדריך זה ראינו כיצד Pandas יכולה לעזור לנו להציג ולנתח מידע מבוסס סדרת זמן. בכלל כך, הצגה טבלאית ובאמצעות תרשים וטכניקה של חלון מתגלגל. במדריך הבא בסדרה תוכלו ללמוד כיצד להשתמש בלמידת מכונה כדי לחזות מנתונים היסטוריים אל העתיד.
לכל המדריכים בנושא של למידת מכונה
אהבתם? לא אהבתם? דרגו!
0 הצבעות, ממוצע 0 מתוך 5 כוכבים
המדריכים באתר עוסקים בנושאי תכנות ופיתוח אישי. הקוד שמוצג משמש להדגמה ולצרכי לימוד. התוכן והקוד המוצגים באתר נבדקו בקפידה ונמצאו תקינים. אבל ייתכן ששימוש במערכות שונות, דוגמת דפדפן או מערכת הפעלה שונה ולאור השינויים הטכנולוגיים התכופים בעולם שבו אנו חיים יגרום לתוצאות שונות מהמצופה. בכל מקרה, אין בעל האתר נושא באחריות לכל שיבוש או שימוש לא אחראי בתכנים הלימודיים באתר.
למרות האמור לעיל, ומתוך רצון טוב, אם נתקלת בקשיים ביישום הקוד באתר מפאת מה שנראה לך כשגיאה או כחוסר עקביות נא להשאיר תגובה עם פירוט הבעיה באזור התגובות בתחתית המדריכים. זה יכול לעזור למשתמשים אחרים שנתקלו באותה בעיה ואם אני רואה שהבעיה עקרונית אני עשוי לערוך התאמה במדריך או להסיר אותו כדי להימנע מהטעיית הציבור.
שימו לב! הסקריפטים במדריכים מיועדים למטרות לימוד בלבד. כשאתם עובדים על הפרויקטים שלכם אתם צריכים להשתמש בספריות וסביבות פיתוח מוכחות, מהירות ובטוחות.
המשתמש באתר צריך להיות מודע לכך שאם וכאשר הוא מפתח קוד בשביל פרויקט הוא חייב לשים לב ולהשתמש בסביבת הפיתוח המתאימה ביותר, הבטוחה ביותר, היעילה ביותר וכמובן שהוא צריך לבדוק את הקוד בהיבטים של יעילות ואבטחה. מי אמר שלהיות מפתח זו עבודה קלה ?
השימוש שלך באתר מהווה ראייה להסכמתך עם הכללים והתקנות שנוסחו בהסכם תנאי השימוש.