ניתוח והצגת סדרות נתונים מבוססות זמן באמצעות pandas

מחבר:
בתאריך:

בסדרה מבוססת זמן (time series) ערכים מספריים מסודרים לפי זמנים. לדוגמה, מדידות הטמפרטורה מדי שעה או מחיר הסגירה היומי של מנייה בבורסה. Pandas הוא כלי מצוין להצגה ולניתוח סדרות מבוססות זמן. במדריך זה נדגים את היכולת הזו באמצעות ניתוח המחיר של מניות גוגל ואמזון לאורך שנים.

המדריך מבוסס על שני מדריכים קודמים בסדרה שמומלץ לקרוא אותם (אם כי לא חייבים כי המדריך עומד בפני עצמו):

 

סביבת העבודה

כדי לתרגל את הקוד במדריך נייבא 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()

תרשים של מחירים באמצעות pandas

 

מה לעשות עם מסד נתונים שבו האינדקס מספרי

לא תמיד יפנקו אותנו כל כך ויתנו לנו מסד נתונים שבו האינדקס מבוסס על תאריכים ואז נקבל לרוב אינדקס מבוסס מספרים. בחלק זה של המדריך נלמד כיצד להתמודד עם המצב שבו קיבלנו מסד נתונים שבו האינדקס מספרי ולא מבוסס תאריך.

כדי ללמוד, נשנה את האינדקס של מסד הנתונים לאינדקס מספרי.

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()

pandas diagram index year 2017

מה לגבי המחירים בחודש ספציפי?

# monthly
goog['2017-10']['Close'].plot()

pandas diagram index year 2017 month october

והכי נחמד שאפשר לראות את הנתונים עבור טווח של תאריכים.

# within a range
goog['2017-09-05':'2017-10-25']['Close'].plot()

pandas diagram within a time range

 

שינוי התדירות של סדרות מבוססות זמן

מסד הנתונים שלנו מבוסס על נתונים יומיים אבל הרבה פעמים אנחנו נרצה לשנות את תדירות ההצגה. לדוגמה, להציג מחירים על בסיס תדירות שבועית, חודשית, רבעונית. אז גם לזה יש ל-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()

pandas resample monthly mean

מה בנוגע לממוצע הריבעוני של המחירים על פני התקופה כולה?

# quarterly
goog.Close.resample('Q').mean().plot()

pandas resample quaterly mean

ישנם פרמטרים נוספים שניתן להעביר. תוכלו למצוא אותם בתיעוד הרשמי של הספרייה : Pandas time series offset aliases

 

השוואה בין מחירי מניות

כדי ליצור מסד נתונים שמשווה בין מחירי המניות של אמזון וגוגל נעשה את הצעדים הבאים:

  1. ניצור שני dataframe נפרדים עבור המחיר של כל מנייה מהעמודה Close.
  2. נשנה את שם העמודה לפי שם המנייה כדי שנוכל לדעת לאיזו מניה שייכים הנתונים.
  3. נחבר את שני ה-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 GOOGLE 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()

pandas compare 2 stocks over a period of years

 

חלון מתגלגל

חלון מתגלגל (rolling window) היא טכניקה חשובה לניתוח של נתונים מבוססי זמן כי היא "מחליקה" את תצוגת הנתונים ובכך מקלה עלינו לראות מגמות ארוכות טווח.

המתודה rolling היא הפונקציה של Pandas שיוצרת את החלון המתגלגל. תמיד מוסיפים לה פונקצית אגרגציה (mean, std, median, sum). לדוגמה, חלון מתגלגל על פני 21 נקודות זמן.

stocks.rolling(21).mean().plot()

pandas rolling window 21 days

נשווה לחלון מתגלגל המבוסס על חמישה ימים.

stocks.rolling(5).mean().plot()

pandas rolling window 5 days

אפשר לראות שהנתונים על פני 21 יום הם חלקים יותר.

 

סיכום

במדריך זה ראינו כיצד Pandas יכולה לעזור לנו להציג ולנתח מידע מבוסס סדרת זמן. בכלל כך, הצגה טבלאית ובאמצעות תרשים וטכניקה של חלון מתגלגל. במדריך הבא בסדרה תוכלו ללמוד כיצד להשתמש בלמידת מכונה כדי לחזות מנתונים היסטוריים אל העתיד.

לכל המדריכים בנושא של למידת מכונה

 

אהבתם? לא אהבתם? דרגו!

0 הצבעות, ממוצע 0 מתוך 5 כוכבים

 

 

הוסף תגובה חדשה

 

= 8 + 2