在使用Python進(jìn)行數(shù)據(jù)分析時(shí),經(jīng)常會(huì)遇到時(shí)間日期格式處理、轉(zhuǎn)換和時(shí)間索引,Pandas作為Python環(huán)境下的數(shù)據(jù)分析庫(kù),提供了一套標(biāo)準(zhǔn)的時(shí)間序列處理工具和算法,使我們可以非常高效的處理時(shí)間序列,比如切片、聚合、重采樣等等。這些強(qiáng)大的日期數(shù)據(jù)處理功能,是處理日期時(shí)間序列的利器。
pandas 支持 4 種常見(jiàn)時(shí)間概念:
> 1. 日期時(shí)間(Datetime):帶時(shí)區(qū)的日期時(shí)間,類(lèi)似于標(biāo)準(zhǔn)庫(kù)的 `datetime.datetime` 。
> 2. 時(shí)間差(Timedelta):絕對(duì)時(shí)間周期,類(lèi)似于標(biāo)準(zhǔn)庫(kù)的 `datetime.timedelta`。
> 3. 時(shí)間段(Timespan):在某一時(shí)點(diǎn)以指定頻率定義的時(shí)間跨度。
> 4. 日期偏移(Dateoffset):與日歷運(yùn)算對(duì)應(yīng)的時(shí)間段,類(lèi)似于 `dateutil` 的 `dateutil.relativedelta.relativedelta`。
一般情況下,時(shí)間序列主要是 Series 或 DataFrame的時(shí)間型索引,可以用時(shí)間元素進(jìn)行操控。
時(shí)間戳
時(shí)間戳是最基本的時(shí)間序列數(shù)據(jù),用于把數(shù)值與時(shí)點(diǎn)關(guān)聯(lián)在一起。Pandas 對(duì)象通過(guò)時(shí)間戳調(diào)用時(shí)點(diǎn)數(shù)據(jù)。
時(shí)間戳的創(chuàng)建
在pandas中提供了Timestamp()可以用于創(chuàng)建一個(gè)時(shí)間戳對(duì)象。
import datetime
import pandas as pd
# 三種方式
pd.Timestamp(datetime.datetime(2021, 8, 16)) # 結(jié)果: Timestamp('2021-08-16 00:00:00')
pd.Timestamp(2021, 8, 16) # 結(jié)果: Timestamp('2021-08-16 00:00:00')
pd.Timestamp('2021-08-16') # 結(jié)果: Timestamp('2021-08-16 00:00:00')
to_datetime()轉(zhuǎn)換得到時(shí)間戳
import pandas as pd
pd.to_datetime('2021/08/08') # 結(jié)果:Timestamp('2021-08-08 00:00:00')
to_datetime` 轉(zhuǎn)換單個(gè)字符串時(shí),返回的是單個(gè) `Timestamp`。`Timestamp` 僅支持字符串輸入,不支持 `dayfirst`、`format` 等字符串解析選項(xiàng),如果要使用這些選項(xiàng),就要用 `to_datetime`。
要實(shí)現(xiàn)精準(zhǔn)轉(zhuǎn)換,除了傳遞 `datetime` 字符串,還要指定 `format` 參數(shù),指定此參數(shù)還可以加速轉(zhuǎn)換速度。
pd.to_datetime('2021/08/08', format='%Y/%m/%d')
pd.to_datetime('08-08-2021 00:00', format='%d-%m-%Y %H:%M')
返回結(jié)果也是一個(gè)`Timestamp`類(lèi)型。當(dāng)然如果不可解析則出發(fā)錯(cuò)誤
pd.to_datetime(['2021/08/31', 'abc'], errors='raise') # 報(bào)錯(cuò)ValueError: Unknown string format
轉(zhuǎn)換多個(gè)時(shí)間序列
import pandas as pd
pd.to_datetime(pd.Series(["Aug 16, 2021", "2021-08-17", None]))
結(jié)果(其中Pandas 用 `NaT` 表示日期時(shí)間、時(shí)間差及時(shí)間段的空值,代表了缺失日期或空日期的值,類(lèi)似于浮點(diǎn)數(shù)的 `np.nan`)
0 2021-08-16
1 2021-08-17
2 NaT
dtype: datetime64[ns]
當(dāng)然也可以使用如下方式:
pd.to_datetime(["2021/08/16", "2021.08.17"]) #也可以轉(zhuǎn)成時(shí)間戳的格式
返回結(jié)果與上面的有所不同,返回值不是一個(gè)序列而是一個(gè)DatetimeIndex類(lèi)型
DatetimeIndex(['2021-08-16', '2021-08-17'], dtype='datetime64[ns]', freq=None)
date_range()獲取時(shí)間戳范圍
實(shí)際工作中,經(jīng)常要生成含大量時(shí)間戳的超長(zhǎng)索引,一個(gè)個(gè)輸入時(shí)間戳又枯燥,又低效。如果時(shí)間戳是定頻的,用 `date_range()`與 `bdate_range()`函數(shù)即可創(chuàng)建 `DatetimeIndex`。`date_range` 默認(rèn)的頻率是**日歷日**,`bdate_range` 的默認(rèn)頻率是**工作日
pd.date_range(start=None,end=None,periods=None,freq=None)
Return a fixed frequency DatetimeIndex.
start:表示起始
end:表示結(jié)尾
periods:表示時(shí)間段
freq:表示有倍數(shù)的頻率字符串,e.g. '5H'.
pd.date_range("2021-8-8", periods=8) # 表示從2021-8-8開(kāi)始到現(xiàn)在日期的8個(gè)時(shí)間
輸出結(jié)果:
DatetimeIndex(['2021-08-08', '2021-08-09', '2021-08-10', '2021-08-11',
'2021-08-12', '2021-08-13', '2021-08-14', '2021-08-15'],
dtype='datetime64[ns]', freq='D')
如果使用bdate_range()則獲取的日期是工作日的日期時(shí)間
pd.bdate_range("2021-8-8", periods=8)
結(jié)果:
DatetimeIndex(['2021-08-09', '2021-08-10', '2021-08-11', '2021-08-12',
'2021-08-13', '2021-08-16', '2021-08-17', '2021-08-18'],
dtype='datetime64[ns]', freq='B')
如果指定freq,date_range 默認(rèn)使用的頻率是 日歷日即`D`,也可以通過(guò)freq修改成周的。
pd.date_range("2021-8-8", periods=8, freq="W")
輸出結(jié)果下:
DatetimeIndex(['2021-08-08', '2021-08-15', '2021-08-22', '2021-08-29',
'2021-09-05', '2021-09-12', '2021-09-19', '2021-09-26'],
dtype='datetime64[ns]', freq='W-SUN')
時(shí)間序列的頻率表:
時(shí)間段
時(shí)間段的創(chuàng)建
pandas提供了`Period`類(lèi)型,它是基于`numpy.datetime64`編碼的固定頻率間隔。與之相關(guān)的索引類(lèi)型是`PeriodIndex`。`Period` 表示的時(shí)間段更直觀,還可以用日期時(shí)間格式的字符串進(jìn)行推斷。默認(rèn)是月`M`,也可以是天`D`
pd.Period('2021-08')
pd.Period('2021-05', freq='D')
返回:
Period('2021-08', 'M')
Period('2021-05-01', 'D')
時(shí)間段的范圍創(chuàng)建
pd.period_range('2020-08',periods=5,freq='M')
pd.period_range('2020-08',periods=5,freq='D')
結(jié)果是時(shí)間段序列:
PeriodIndex(['2020-08', '2020-09', '2020-10', '2020-11', '2020-12'], dtype='period[M]', freq='M')
PeriodIndex(['2020-08-01', '2020-08-02', '2020-08-03', '2020-08-04',
'2020-08-05'],
dtype='period[D]', freq='D')
Pandas 可以識(shí)別兩種表現(xiàn)形式,并在兩者之間進(jìn)行轉(zhuǎn)化。Pandas 后臺(tái)用 `Timestamp` 實(shí)例代表時(shí)間戳,用 `DatetimeIndex` 實(shí)例代表時(shí)間戳序列。pandas 用 `Period` 對(duì)象表示符合規(guī)律的時(shí)間段標(biāo)量值,用 `PeriodIndex` 表示時(shí)間段序列。
時(shí)間索引
`DatetimeIndex` 主要用作 pandas 對(duì)象的索引。`DatetimeIndex` 類(lèi)為時(shí)間序列做了很多優(yōu)化:
1. 預(yù)計(jì)算了各種偏移量的日期范圍,并在后臺(tái)緩存,讓后臺(tái)生成后續(xù)日期范圍的速度非???僅需抓取切片)。
2. 在 pandas 對(duì)象上使用 `shift` 與 `tshift` 方法進(jìn)行快速偏移。
3. 合并具有相同頻率的重疊 `DatetimeIndex` 對(duì)象的速度非常快(這點(diǎn)對(duì)快速數(shù)據(jù)對(duì)齊非常重要)。
4. 通過(guò) `year`、`month` 等屬性快速訪問(wèn)日期字段。
5. `snap` 等正則函數(shù)與超快的 `asof` 邏輯
DatetimeIndex` 可以當(dāng)作常規(guī)索引,支持選擇、切片等方法。
index = pd.date_range('2020-12-01','2021-10-01' , freq='BM') # 指定范圍內(nèi)的每個(gè)月的最后一個(gè)工作日
ts = pd.Series(np.random.randn(len(index)), index=index)
輸出結(jié)果:
2020-12-31 -0.351660
2021-01-29 0.358744
2021-02-26 0.746602
2021-03-31 0.178684
2021-04-30 -0.408984
2021-05-31 0.117038
2021-06-30 0.661603
2021-07-30 0.655608
2021-08-31 -0.207675
2021-09-30 -0.023105
Freq: BM, dtype: float64
可以支持獲取index和索引切片
display(ts.index)
display(ts[:4].index)
display(ts[::2].index)
輸出結(jié)果:
DatetimeIndex(['2020-12-31', '2021-01-29', '2021-02-26', '2021-03-31',
'2021-04-30', '2021-05-31', '2021-06-30', '2021-07-30',
'2021-08-31', '2021-09-30'],
dtype='datetime64[ns]', freq='BM')
DatetimeIndex(['2020-12-31', '2021-01-29', '2021-02-26', '2021-03-31'], dtype='datetime64[ns]', freq='BM')
DatetimeIndex(['2020-12-31', '2021-02-26', '2021-04-30', '2021-06-30',
'2021-08-31'],
dtype='datetime64[ns]', freq='2BM')
當(dāng)然也可以按照年、月、日獲取時(shí)間索引,或者獲取所有的索引的年月等
display(ts['2020'])
display(ts['2021-06'])
display(ts['2020-12':'2021-05'])
display(ts.index.year)
2020-12-31 -0.35166
Freq: BM, dtype: float64
2021-06-30 0.661603
Freq: BM, dtype: float64
2020-12-31 -0.351660
2021-01-29 0.358744
2021-02-26 0.746602
2021-03-31 0.178684
2021-04-30 -0.408984
2021-05-31 0.117038
Freq: BM, dtype: float64
Int64Index([2020, 2021, 2021, 2021, 2021, 2021, 2021, 2021, 2021, 2021], dtype='int64')
當(dāng)然,`Series` 的值為 `datetime` 時(shí),還可以用 `.dt` 訪問(wèn)這些屬性。
df = pd.DataFrame(np.random.randn(10000, 2),columns=['A','B'])
df['datetime'] = pd.date_range('20180101', periods=10000, freq='H')
df
輸出結(jié)果:
此時(shí)可以通過(guò)datetime列獲取年月日時(shí)分秒等
df['datetime'].dt.year
# df['datetime'].dt.month
# df['datetime'].dt.day
dt的屬性如下表:
時(shí)間差
使用pandas中的Timedelta()函數(shù)表示時(shí)間差,這個(gè)方法與Python基礎(chǔ)中datetime.timedelta是等效的可以互換的在大多數(shù)情況下。
以時(shí)間差為數(shù)據(jù)的 `Series` 與 `DataFrame` 支持各種運(yùn)算,`datetime64 [ns]` 序列或 `Timestamps` 減法運(yùn)算生成的是`timedelta64 [ns]` 序列
直接使用符號(hào)獲取時(shí)間差
pd.to_datetime('2021-8-14') - pd.to_datetime('2021-6-1')
返回值就是一個(gè)Timedelta類(lèi)型的
Timedelta('74 days 00:00:00')
如果想在當(dāng)前的日期前三天或者后5天的值,則需要?jiǎng)?chuàng)建時(shí)間差對(duì)象
from datetime import datetime
delta = pd.Timedelta('3 days')
display(datetime.now()-delta)
delta1 = pd.Timedelta('30 days')
display(datetime.now()+delta1)
輸出結(jié)果(默認(rèn)是ns作為單位):
datetime.datetime(2021, 8, 14, 15, 21, 9, 875792)
datetime.datetime(2021, 9, 16, 15, 21, 9, 877299)
創(chuàng)建Timedelta()可以支持的unit參數(shù)值
delta1 = pd.Timedelta(5,unit='W') # 表示5周后的時(shí)間
display(datetime.now()+delta1)
簡(jiǎn)單應(yīng)用:獲取1998-12-20到現(xiàn)在的年齡
age = (datetime.now()- pd.to_datetime('1998-12-20')) / pd.Timedelta(days=365)
print(age)
結(jié)果:
22.675737040520104
時(shí)間偏移
`DateOffset` 類(lèi)似于時(shí)間差 `Timedelta` ,但遵循指定的日歷日規(guī)則。例如,`Timedelta` 表示的每日時(shí)間差一直都是 24 小時(shí),而 `DateOffset` 的每日偏移量則是與下一天相同的時(shí)間差,使用夏時(shí)制時(shí),每日偏移時(shí)間有可能是 23 或 24 小時(shí),甚至還有可能是 25 小時(shí)。不過(guò),`DateOffset` 子類(lèi)只能是等于或小于**小時(shí)**的時(shí)間單位(`Hour`、`Minute`、`Second`、`Milli`、`Micro`、`Nano`),操作類(lèi)似于 `Timedelta`及對(duì)應(yīng)的絕對(duì)時(shí)間。
DateOffset` 基礎(chǔ)操作類(lèi)似于 `dateutil.relativedelta`可按指定的日歷日時(shí)間段偏移日期時(shí)間。
ts = pd.Timestamp('2016-10-30 00:00:00', tz='Europe/Helsinki') # 其中 tz='Europe/Helsinki'表示夏時(shí)制時(shí)區(qū)
display(ts + pd.Timedelta(days=1))
display(ts + pd.DateOffset(days=1))
輸出結(jié)果:
Timestamp('2016-10-30 23:00:00+0200', tz='Europe/Helsinki')
Timestamp('2016-10-31 00:00:00+0200', tz='Europe/Helsinki')
`DateOffset`可用算數(shù)運(yùn)算符(+)或 `apply` 方法執(zhí)行日期偏移操作。
d = pd.Timestamp('2021-08-15')
two_business_days = 2 * pd.offsets.BDay() # BDay()表示工作日
two_business_days.apply(d) # Timestamp('2021-08-17 00:00:00')
`DateOffset` 還支持 `rollforward()` 與 `rollback()` 方法,按偏移量把某一日期**向前**或**向后**移動(dòng)至有效偏移日期。例如,工作日偏移滾動(dòng)日期時(shí)會(huì)跳過(guò)周末(即,星期六與星期日),直接到星期一,因?yàn)楣ぷ魅掌漆槍?duì)的是工作日。
可以為 `Series` 或 `DatetimeIndex` 里的每個(gè)元素應(yīng)用偏移。
rng = pd.date_range('2021-01-01', '2021-08-16')
s = pd.Series(rng)
s + pd.DateOffset(days=2) # 或者使用s+pd.offsets.Day(2)
輸出結(jié)果:
0 2021-01-03
1 2021-01-04
2 2021-01-05
3 2021-01-06
4 2021-01-07
223 2021-08-14
224 2021-08-15
225 2021-08-16
226 2021-08-17
227 2021-08-18
Length: 228, dtype: datetime64[ns]
與時(shí)間序列相關(guān)的方法
在做時(shí)間序列相關(guān)的工作時(shí),經(jīng)常要對(duì)時(shí)間做一些移動(dòng)/滯后、頻率轉(zhuǎn)換、采樣等相關(guān)操作,我們來(lái)看下這些操作如何使用吧。
移動(dòng)
如果你想移動(dòng)或滯后時(shí)間序列,你可以使用 shift 方法。
ts = pd.Series(np.random.randn(4), index = pd.date_range('2012-01-01',periods =4, freq ='M'))
print(ts)
2012-01-31 1.132395
2012-02-29 0.740404
2012-03-31 0.154164
2012-04-30 -0.487571
Freq: M, dtype: float64
print(ts.shift(2)) #將數(shù)據(jù)往后移動(dòng), 往前移動(dòng)則為 ts.shift(-2)
2012-01-31 NaN
2012-02-29 NaN
2012-03-31 1.132395
2012-04-30 0.740404
Freq: M, dtype: float64
當(dāng)然也可以結(jié)合頻度
print(ts.shift(2, freq='M')) # 此時(shí)時(shí)間增加了2個(gè)月
結(jié)果:
2012-03-31 1.132395
2012-04-30 0.740404
2012-05-31 0.154164
2012-06-30 -0.487571
Freq: M, dtype: float64
改變頻率
使用函數(shù) `asfreq()`。對(duì)于 `DatetimeIndex`,這就是一個(gè)調(diào)用 `reindex()`,并生成 `date_range` 的便捷打包器。
from pandas.tseries.offsets import *
ts = pd.Series(np.random.randn(2), index = pd.date_range('2021-06-01',periods =2, freq ='w'))
ts.asfreq(Day())
結(jié)果(即原來(lái)是周顯示,現(xiàn)在將頻率由周轉(zhuǎn)為了天):
2021-06-06 0.362032
2021-06-07 NaN
2021-06-08 NaN
2021-06-09 NaN
2021-06-10 NaN
2021-06-11 NaN
2021-06-12 NaN
2021-06-13 -1.720824
Freq: D, dtype: float64
你會(huì)發(fā)現(xiàn)出現(xiàn)了缺失值,因此 Pandas 為你提供了 method 參數(shù)來(lái)填充缺失值。幾種不同的填充方法參考 Pandas 缺失值處理 中 fillna 介紹
ts.asfreq(Day(), method="pad") # 即使用0.362032填充了NaN的值
結(jié)果:
2021-06-06 0.362032
2021-06-07 0.362032
2021-06-08 0.362032
2021-06-09 0.362032
2021-06-10 0.362032
2021-06-11 0.362032
2021-06-12 0.362032
2021-06-13 -1.720824
Freq: D, dtype: float64
重采樣
Pandas 有一個(gè)雖然簡(jiǎn)單,但卻強(qiáng)大、高效的功能,可在頻率轉(zhuǎn)換時(shí)執(zhí)行重采樣,如,將秒數(shù)據(jù)轉(zhuǎn)換為 5 分鐘數(shù)據(jù),這種操作在金融等領(lǐng)域里的應(yīng)用非常廣泛。
重采樣(resampling)指的是將時(shí)間序列從一個(gè)頻率轉(zhuǎn)換到另一個(gè)頻率的處理過(guò)程。將高頻數(shù)據(jù)聚合到低頻稱(chēng)為降采樣(downsampling),將低頻數(shù)據(jù)轉(zhuǎn)換到高頻則稱(chēng)為升采樣(upsampling)。除此以外還存在一種采樣方式既不是升采樣,也不是降采樣,比如`W-WED`轉(zhuǎn)換成`W-FRI`。
可以通過(guò)`resample()`函數(shù)來(lái)實(shí)現(xiàn),也可以通過(guò)更簡(jiǎn)單的方式`asfreq()`函數(shù)來(lái)實(shí)現(xiàn)。兩者基本的不同點(diǎn)在于`resample()`是一種數(shù)據(jù)聚合方式`asfreq()`是一種數(shù)據(jù)選取方式。
resample() 是基于時(shí)間的分組操作,每個(gè)組都遵循歸納方法。可以按照分鐘、小時(shí)、工作日、周、月、年等來(lái)作為日期維度
# 獲取7月1日到7月31日的時(shí)間區(qū)間
rng = pd.date_range(start='2021/07/1',end='2021/07/31',freq='D')
# 使用此時(shí)間區(qū)間構(gòu)建一個(gè)Series對(duì)象
ts = pd.Series(np.random.randint(0, 500, len(rng)), index=rng)
# 獲取此Series對(duì)象每5天的數(shù)據(jù)總和
ts.resample('5D').sum()
案例演示
AAPL = pd.read_csv('AAPL.csv')
AAPL.Date = pd.to_datetime(AAPL.Date)
AAPL.head()
AAPL['month'] = AAPL.Date.dt.month
AAPL.groupby('month')['Adj Close'].mean() # 每個(gè)月份的Adj Close的均值
AAPL.set_index('Date').resample('Y')['Adj Close'].mean() # 獲取每年的Adj Close的均值
如果使用resample進(jìn)行重采樣,獲取日期每十年`Adj Close`的均值
AAPL.set_index('Date').resample('10Y')['Adj Close'].mean()
AAPL.csv數(shù)據(jù)回復(fù)`時(shí)間序列重采樣`獲取
更多關(guān)于“Python培訓(xùn)”的問(wèn)題,歡迎咨詢(xún)千鋒教育在線名師。千鋒教育多年辦學(xué),課程大綱緊跟企業(yè)需求,更科學(xué)更嚴(yán)謹(jǐn),每年培養(yǎng)泛IT人才近2萬(wàn)人。不論你是零基礎(chǔ)還是想提升,都可以找到適合的班型,千鋒教育隨時(shí)歡迎你來(lái)試聽(tīng)。