# Pandas的日期與時間結構 ###### tags: `backend` Pandas一開始是為了對財務建模的背景下被開發而成的,因此它包含用於處理日期dates,時間times和時間索引time-indexed數據的廣泛的工具集。 日期與時間有很多手法與格式來結構與展現, 本教程主要會討論: * **Time stamps**: `時間戳記`會參考特定的時間點(例如2015年7月4日上午7:00)。 * **Time intervals**與**periods**: `時間間隔`和`時間段`指的是特定`起點`和`終點`之間的時間長度;例如2015年。時間段通常是指時間間隔的一種特殊情況,其中每個時間間隔的長度均等且不重疊(例如,24 hour-long periods會組成days)。 * **Time deltas** 或 **durations**: `時間增量`或`持續時間`指的是確切的時間長度(例如22.56秒的duration)。 我們將介紹如何使用Pandas中的每種類型的日期/時間數據。 ## Python中的日期和時間 Python的標準函式庫`datetime`本身就有許多可用的日期dates,時間times,增量deltas和時間跨度timespans的表示形式。 ### Python日期和時間函式庫: `datetime` 與 `dateutil` Python處理日期和時間的基本類別位於內置的`datetime`模組中。搭配一個第三方`dateutil`模組,我們便可以在日期和時間的數據上執行一系列有用的分析與處理。例如,使用datetime類別 i來構建`datetime`: ```python from datetime import datetime dt = datetime(year=2015, month=7, day=4) print(type(dt)) print(dt) ``` <class 'datetime.datetime'> 2015-07-04 00:00:00 或者,使用`dateutil`模組,我們可以解析各種字串格式的日期: ```python from dateutil import parser dt = parser.parse("4th of July, 2015") print(type(dt)) print(dt) ``` <class 'datetime.datetime'> 2015-07-04 00:00:00 擁有datetime物件後,你可以執行類似打印星期幾的操作: ```python print(dt.strftime('%A')) ``` Saturday 在以上範例,我們使用了一種標準的字符串格式碼來打即日期(`%A`),你可以在Python的[datetime文檔](https://docs.python.org/3/library/datetime.html)的[strftime部分](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior)中了解更多細節。 其它有用的date utilities的文件可以在[dateutil的線上文件](http://labix.org/python-dateutil)中找到。 `pytz`是一個值得大家注意的函式庫,它包含用於處理時區`timezone`(最易引起數據開發者頭痛的時間序列數據)的工具。 ### NumPy的 `datetime64` Numpy這個專門用來處理數字運算的函式庫特別為日期時間的運算構建了`datetime64`的資料類型。`datetime64` dtype將日期編碼為64-bit的integer,因此可以非常緊湊地表示日期時間。 例如,構建一個基於日期的datetime: ```python import numpy as np dt = np.datetime64('2015-07-04') print(type(dt)) print(dt) ``` <class 'numpy.datetime64'> 2015-07-04 構建基於分鐘精度的datetime: ```python dt = np.datetime64('2015-07-04 12:00') print(type(dt)) print(dt) ``` <class 'numpy.datetime64'> 2015-07-04T12:00 你可以使用多種格式代碼之一來強制使用任何所需的時間基本單位。例如,在這裡,我們將強制基於納秒 `nanosecond-based`的時間: ```python dt = np.datetime64('2015-07-04 12:59:59.50', 'ns') print(type(dt)) print(dt) ``` <class 'numpy.datetime64'> 2015-07-04T12:59:59.500000000 下表列出了可用的格式代碼以及它們可以編碼的相對和絕對時間跨度: |Code|Meaning|Time span (relative)|Time span (absolute)| |:--- |:--- |:--- |:--- | |Y|Year|± 9.2e18 years|[9.2e18 BC, 9.2e18 AD]| |M|Month|± 7.6e17 years|[7.6e17 BC, 7.6e17 AD]| |W|Week|± 1.7e17 years|[1.7e17 BC, 1.7e17 AD]| |D|Day|± 2.5e16 years|[2.5e16 BC, 2.5e16 AD]| |h|Hour|± 1.0e15 years|[1.0e15 BC, 1.0e15 AD]| |m|Minute|± 1.7e13 years|[1.7e13 BC, 1.7e13 AD]| |s|Second|± 2.9e12 years|[ 2.9e9 BC, 2.9e9 AD]| |ms|Millisecond|± 2.9e9 years|[ 2.9e6 BC, 2.9e6 AD]| |us|Microsecond|± 2.9e6 years|[290301 BC, 294241 AD]| |ns|Nanosecond|± 292 years|[ 1678 AD, 2262 AD]| |ps|Picosecond|± 106 days|[ 1969 AD, 1970 AD]| |fs|Femtosecond|± 2.6 hours|[ 1969 AD, 1970 AD]| |as|Attosecond|± 9.2 seconds|[ 1969 AD, 1970 AD]| 對於我們在現實世界中看到的日期時間數據類型,一般的預設值為`datetime64[ns]`,因為它可以以適當的精度對有用的現代日期範圍進行編碼。 ### Pandas的日期和時間 Pandas在剛才討論的所有工具的基礎上,提供了一個`Timestamp`類別,該類別將`datetime`和`dateutil`的易用性與`numpy.datetime64`的有效存儲和向量化接口結合在一起。 從一組`Timestamp`物件中,Pandas可以構造一個`DatetimeIndex`,該索引可用於為`Series`或`DataFrame`中的數據建立索引。我們將在下面看到許多範例。 ```python import pandas as pd dt = pd.to_datetime("4th of July, 2015") print(type(dt)) print(dt) print(dt.strftime('%A')) ``` <class 'pandas._libs.tslibs.timestamps.Timestamp'> 2015-07-04 00:00:00 Saturday 此外,我們也可以直接在同一`Timestamp`物件上執行NumPy風格的向量化操作: ```python dti = dt + pd.to_timedelta(np.arange(12), 'D') print(type(dti)) print(dti) ``` <class 'pandas.core.indexes.datetimes.DatetimeIndex'> DatetimeIndex(['2015-07-04', '2015-07-05', '2015-07-06', '2015-07-07', '2015-07-08', '2015-07-09', '2015-07-10', '2015-07-11', '2015-07-12', '2015-07-13', '2015-07-14', '2015-07-15'], dtype='datetime64[ns]', freq=None) 接下來讓我們將介紹如何使用Pandas提供的工具來處理時間序列數據。 ## Pandas時間序列:按時間編制索引 當開始通過時間戳`timestamp`為數據建立索引時,Pandas時間序列工具就可以發揮很大的作用了。例如,我們可以構造一個具有時間索引數據的`Series`物件: ```python dti = pd.DatetimeIndex(['2014-07-04', '2014-08-04', '2015-07-04', '2015-08-04']) data = pd.Series([0, 1, 2, 3], index=dti) print(type(data)) print(data) ``` <class 'pandas.core.series.Series'> 2014-07-04 0 2014-08-04 1 2015-07-04 2 2015-08-04 3 dtype: int64 透過這個時間的索引, 我們可以很輕易地以時間的概念來對數據進行選取: ```python data2 = data['2014-07-04':'2015-07-04'] print(type(data2)) print(data2) ``` <class 'pandas.core.series.Series'> 2014-07-04 0 2014-08-04 1 2015-07-04 2 dtype: int64 或是指定某些日期的格式來選取數據: ```python data3 = data['2015'] print(type(data3)) print(data3) ``` <class 'pandas.core.series.Series'> 2015-07-04 2 2015-08-04 3 dtype: int64 ## Pandas 時間序列數據結構 日期與時間格式與結構: * **Time stamps**: `時間戳記`會參考特定的時間點(例如2015年7月4日上午7:00)。 * **Time intervals**與**periods**: `時間間隔`和`時間段`指的是特定`起點`和`終點`之間的時間長度;例如2015年。時間段通常是指時間間隔的一種特殊情況,其中每個時間間隔的長度均等且不重疊(例如,24 hour-long periods會組成days)。 * **Time deltas** 或 **durations**: `時間增量`或`持續時間`指的是確切的時間長度(例如22.56秒的duration)。 接著介紹用於處理時間序列數據的基本Pandas數據結構: * **Time stamps**: Pandas提供`Timestamp`類型。如前所述,它實質上是Python內建模組`datetime`的替代品,但它基於更有效的`numpy.datetime64`數據類型。關聯到Dataframe的索引結構是`DatetimeIndex`。 * **Time intervals**與**periods**: Pandas提供了`Period`類型。這將基於`numpy.datetime64`編碼固定頻率的間隔。關聯到Dataframe的索引結構是`PeriodIndex`。 * **Time deltas** 或 **durations**: Pandas提供`Timedelta`類型。 `Timedelta`是基於`numpy.timedelta64`是Python內建模組`datetime.timedelta`類型的更有效替代品。關聯到Dataframe的索引結構是`TimedeltaIndex`。 這些date/time物件中最核心的是`Timestamp`和`DatetimeIndex`物件。儘管可以直接構建這些物件,但更常見的是使用`pd.to_datetime()`函數,該函數可以解析各種日期時間格式。將單個日期傳遞給`pd.to_datetime()`會產生一個`Timestamp`物件;預設情況下,傳遞一系列的日期則會產生`DatetimeIndex`物件: ```python dates = pd.to_datetime([datetime(2015, 7, 3), '4th of July, 2015', '2015-Jul-6', '07-07-2015', '20150708']) print(type(dates)) print(dates) print(dates[0]) print(type(dates[0])) dates[0] ``` <class 'pandas.core.indexes.datetimes.DatetimeIndex'> DatetimeIndex(['2015-07-03', '2015-07-04', '2015-07-06', '2015-07-07', '2015-07-08'], dtype='datetime64[ns]', freq=None) 2015-07-03 00:00:00 <class 'pandas._libs.tslibs.timestamps.Timestamp'> Timestamp('2015-07-03 00:00:00') 可以使用`to_period()`函數並添加`頻率代碼`將任何`DatetimeIndex`轉換為`PeriodIndex`。在這裡,我們將使用'`D`'來表示每日頻率: ```python dates_d_freq = dates.to_period('D') print(type(dates_d_freq)) print(dates_d_freq) print(dates_d_freq[0]) print(type(dates_d_freq[0])) dates_d_freq[0] ``` <class 'pandas.core.indexes.period.PeriodIndex'> PeriodIndex(['2015-07-03', '2015-07-04', '2015-07-06', '2015-07-07', '2015-07-08'], dtype='period[D]', freq='D') 2015-07-03 <class 'pandas._libs.tslibs.period.Period'> Period('2015-07-03', 'D') 當從另一個日期減去日期時,將創建一個`TimedeltaIndex`: ```python dates_substracted = dates - dates[0] print(type(dates_substracted)) print(dates_substracted) print(dates_substracted[0]) print(type(dates_substracted[0])) dates_substracted[0] ``` <class 'pandas.core.indexes.timedeltas.TimedeltaIndex'> TimedeltaIndex(['0 days', '1 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq=None) 0 days 00:00:00 <class 'pandas._libs.tslibs.timedeltas.Timedelta'> Timedelta('0 days 00:00:00') ### 構建時間序列:`pd.date_range()` 為了使日期時間序列的創建更加方便,Pandas為此提供了一些功能:`pd.date_range()`用於`Timestamp`,`pd.period_range()`用於`Period`,`pd.timedelta_range()`用於`Timedelta`。 `pd.date_range()`接受start date,end date和optional frequency code ,來構建日期序列。預設情況下,頻率為one day: ```python dates = pd.date_range('2015-07-03', '2015-07-10') print(type(dates)) print(dates) print(dates[0]) print(type(dates[0])) dates[0] ``` 另外,日期範圍可以不指定起點和終點,而可以指定起點和多個時間段: ```python dates = pd.date_range('2015-07-03', periods=8) print(type(dates)) print(dates) print(dates[0]) print(type(dates[0])) dates[0] ``` 可以通過更改`freq`參數來修改時間間隔,該參數默認為`D`。例如,在這裡,我們將構建一個`小時`時間戳範圍: ```python dates = pd.date_range('2015-07-03', periods=8, freq='H') print(type(dates)) print(dates) print(dates[0]) print(type(dates[0])) dates[0] ``` 要創建`Period`或`Timedelta`的規則序列時`pd.period_range()`和`pd.timedelta_range()`函數很有用。以下是以月份為頻率的資料: ```python date_periods = pd.period_range('2015-07', periods=8, freq='M') print(type(date_periods)) print(date_periods) print(date_periods[0]) print(type(date_periods[0])) date_periods[0] ``` 一個持續時間sequence, 每個值會遞增一個小時: ```python time_deltas = pd.timedelta_range(0, periods=10, freq='H') print(type(time_deltas)) print(time_deltas) print(time_deltas[0]) print(type(time_deltas[0])) time_deltas[0] ``` 以上幾個範例都需要了解Pandas的頻率代碼,我們將在下一部分中對其進行總結。 ## 頻率間隔(Frequency)與偏移(Offset) Panda時間序列工具的基礎是`頻率間隔`或`日期偏移量`的概念。就像我們在上面看到了`D`(day)和`H`(hour)代碼一樣,我們可以使用這些代碼來指定任何所需的頻率間隔。下表總結了可用的主要代碼: |Code |Description |Code |Description| |:------|:--------------|:------|:----------| |`D` |Calendar day |`B` |Business day| |`W` |Weekly||| |`M` |Month end |`BM` |Business month end| |`Q` |Quarter end |`BQ` |Business quarter end| |`A` |Year end |`BA` |Business year end| |`H` |Hours |`BH` |Business hours| |`T` |Minutes||| |`S` |Seconds||| |`L` |Milliseonds||| |`U` |Microseconds||| |`N` |nanoseconds||| 常常在日期時間的計算, 有時候我們需要指定在某個區間的開頭。我們可通過在頻率間隔代碼上添加`S`後綴來定義: |Code |Description |Code |Description| |:------|:--------------|:------|:----------| |`MS` |Month start |`BMS` |Business month start| |`QS` |Quarter start |`BQS` |Business quarter start| |`AS` |Year start |`BAS` |Business year start| 除此之外,你也可以通過添加後綴三個字母的`月份代碼`來更改用於標記任何季度或年度代碼的月份: * `Q-JAN`, `BQ-FEB`, `QS-MAR`, `BQS-APR`, etc. * `A-JAN`, `BA-FEB`, `AS-MAR`, `BAS-APR`, etc. 相同的方式,你可以通過添加三個字母的`工作日代碼`來修改每週頻率的分割點: * `W-SUN`, `W-MON`, `W-TUE`, `W-WED`, etc. 最重要的是,代碼可以與數字組合來指定其他頻率。例如,對於2小時30分鐘的頻率,我們可以按以下方式組合小時(`H`)和分鐘(`T`)代碼: ```python time_deltas = pd.timedelta_range(0, periods=9, freq='2H30T') print(type(time_deltas)) print(time_deltas) print(time_deltas[0]) print(type(time_deltas[0])) time_deltas[0] ``` 所有這些短代碼都涉及Pandas時間序列偏移量的特定實例,可以在`pd.tseries.offsets`模組中找到它們。例如,我們可以直接創建一個工作日偏移量,如下所示: ```python from pandas.tseries.offsets import BDay dates = pd.date_range('2015-07-01', periods=5, freq=BDay()) print(type(dates)) print(dates) print(dates[0]) print(type(dates[0])) dates[0] ```