一 、時(shí)間序列中的基本對(duì)象
時(shí)間戳鳞青,即'2020-9-7 08:00:00'和'2020-9-7 10:00:00'這兩個(gè)時(shí)間點(diǎn)分別代表了上課和下課的時(shí)刻霸饲,在pandas中稱(chēng)為T(mén)imestamp。同時(shí)臂拓,一系列的時(shí)間戳可以組成DatetimeIndex厚脉,而將它放到Series中后,Series的類(lèi)型就變?yōu)榱薲atetime64[ns]胶惰,如果有涉及時(shí)區(qū)則為datetime64[ns, tz]傻工,其中tz是timezone的簡(jiǎn)寫(xiě)。
時(shí)間差孵滞,即上課需要的時(shí)間精钮,兩個(gè)Timestamp做差就得到了時(shí)間差,pandas中利用Timedelta來(lái)表示剃斧。類(lèi)似的轨香,一系列的時(shí)間差就組成了TimedeltaIndex, 而將它放到Series中后幼东,Series的類(lèi)型就變?yōu)榱藅imedelta64[ns]臂容。
時(shí)間段科雳,即在8點(diǎn)到10點(diǎn)這個(gè)區(qū)間都會(huì)持續(xù)地在上課,在pandas利用Period來(lái)表示脓杉。類(lèi)似的糟秘,一系列的時(shí)間段就組成了PeriodIndex, 而將它放到Series中后球散,Series的類(lèi)型就變?yōu)榱薖eriod尿赚。
日期偏置,假設(shè)你只知道9月的第一個(gè)周一早上8點(diǎn)要去上課蕉堰,但不知道具體的日期凌净,那么就需要一個(gè)類(lèi)型來(lái)處理此類(lèi)需求。再例如屋讶,想要知道2020年9月7日后的第30個(gè)工作日是哪一天涯捻,那么時(shí)間差就解決不了你的問(wèn)題毯盈,從而pandas中的DateOffset就出現(xiàn)了嗜闻。同時(shí)乏冀,pandas中沒(méi)有為一列時(shí)間偏置專(zhuān)門(mén)設(shè)計(jì)存儲(chǔ)類(lèi)型,理由也很簡(jiǎn)單乐疆,因?yàn)樾枨蟊容^奇怪划乖,一般來(lái)說(shuō)我們只需要對(duì)一批時(shí)間特征做一個(gè)統(tǒng)一的特殊日期偏置。
二挤土、時(shí)間戳
Timestamp的構(gòu)造與屬性
- 單個(gè)時(shí)間戳:使用pd.Timestamp
ts = pd.Timestamp('2020/1/1')
ts = pd.Timestamp('2020-1-1 08:10:30')
- 可以通過(guò)year琴庵, month, day耕挨, hour细卧, min, second獲取具體的數(shù)值
Datetime序列的生成
- to_datetime筒占,能夠把一列時(shí)間戳格式的對(duì)象轉(zhuǎn)換成為datetime64[ns]類(lèi)型的時(shí)間序列:
pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])
# 可使用format進(jìn)行格式匹配
temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
- date_range
- start:開(kāi)始時(shí)間
- end:結(jié)束時(shí)間
- freq:時(shí)間間隔
- periods:時(shí)間戳個(gè)數(shù)
pd.date_range('2020-1-1','2020-2-28', freq='10D')
【練一練】
Timestamp
上定義了一個(gè)value
屬性贪庙,其返回的整數(shù)值代表了從1970年1月1日零點(diǎn)到給定時(shí)間戳相差的納秒數(shù),請(qǐng)利用這個(gè)屬性構(gòu)造一個(gè)隨機(jī)生成給定日期區(qū)間內(nèi)日期序列的函數(shù)翰苫。
def rand_date(date_interval, size=10):
start = pd.Timestamp(date_interval[0]).value / 10**9 # 單位轉(zhuǎn)換成s
end = pd.Timestamp(date_interval[1]).value / 10**9 # 單位轉(zhuǎn)換成s
rand = np.random.randint(start, end+1, size=size)
return pd.to_datetime(rand, unit='s')
rand_date(['2020-1-1', '2020-2-1'])
asfreq止邮, 對(duì)給定的序列進(jìn)行類(lèi)似于reindex操作
s = pd.Series(np.random.rand(5), index=pd.to_datetime(['2020-1-%d'%i for i in range(1,10,2)]))
s.asfreq('12H')
【練一練】
前面提到了datetime64[ns]
本質(zhì)上可以理解為一個(gè)大整數(shù),對(duì)于一個(gè)該類(lèi)型的序列奏窑,可以使用max, min, mean
导披,來(lái)取得最大時(shí)間戳、最小時(shí)間戳和“平均”時(shí)間戳埃唯。
date =rand_date(['2020-1-1', '2020-2-1'], size=100) # 隨機(jī)生成100個(gè)日期
print(date.max()) # 最大值
print(date.min()) # 最小值
print(date.mean()) # 平均值
dt對(duì)象撩匕,如同category, string的序列上定義了cat, str來(lái)完成分類(lèi)數(shù)據(jù)和文本數(shù)據(jù)的操作,在時(shí)序類(lèi)型的序列上定義了dt對(duì)象來(lái)完成許多時(shí)間序列的相關(guān)操作墨叛。
- 取出時(shí)間相關(guān)的屬性:
date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter
- 判斷時(shí)間戳是否滿足條件
s.dt.is_year_start # 還可選 is_quarter/month_start
s.dt.is_year_end # 還可選 is_quarter/month_end
- 取整操作
s.dt.round('1H')
s.dt.ceil('1H')
s.dt.floor('1H')
時(shí)間戳的切片與索引
- 利用dt對(duì)象和布爾條件聯(lián)合使用
s[(idx.is_month_start|idx.is_month_end).values].head()
- 利用切片止毕,常用于連續(xù)時(shí)間戳
s['2020-05':'2020-7-15']
三模蜡、時(shí)間差
Timedelta的生成
- 單個(gè), 通過(guò)pd.Timedelta
pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
pd.Timedelta('1 days 25 minutes') # 字符串生成
- 序列扁凛,通過(guò)pd.to_timedelta, 或timedelta_range
s = pd.to_timedelta(df.Time_Record)
pd.timedelta_range('0s', '1000s', freq='6min')
pd.timedelta_range('0s', '1000s', periods=3)
- Timedelta序列也定義了dt對(duì)象忍疾,屬性包括days, seconds, mircroseconds, nanoseconds,它們分別返回了對(duì)應(yīng)的時(shí)間差特征谨朝。需要注意的是卤妒,這里的seconds不是指單純的秒,而是對(duì)天數(shù)取余后剩余的秒數(shù)
Timedelta的運(yùn)算
- 與標(biāo)量的乘法運(yùn)算
- 與時(shí)間戳的加減法運(yùn)算
- 與時(shí)間差的加減法與除法運(yùn)算
四字币、日期偏置
Offset對(duì)象则披,在pd.offsets中被定義。當(dāng)使用+時(shí)獲取離其最近的下一個(gè)日期纬朝,當(dāng)使用-時(shí)獲取離其最近的上一個(gè)日期
- CDay收叶,其中的holidays, weekmask參數(shù)能夠分別對(duì)自定義的日期和星期進(jìn)行過(guò)濾骄呼,前者傳入了需要過(guò)濾的日期列表共苛,后者傳入的是三個(gè)字母的星期縮寫(xiě)構(gòu)成的星期字符串,其作用是只保留字符串中出現(xiàn)的星期
pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
pd.Timestamp('20200907') - pd.offsets.BDay(30)
pd.Timestamp('20200907') + pd.offsets.MonthEnd()
my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
dr = pd.date_range('20200108', '20200111')
dr.to_series().dt.dayofweek
偏置字符串
pd.date_range('20200101','20200331', freq='MS') # 月初
pd.date_range('20200101','20200331', freq='M') # 月末
pd.date_range('20200101','20200110', freq='B') # 工作日
pd.date_range('20200101','20200201', freq='W-MON') # 周一
pd.date_range('20200101','20200201', freq='WOM-1MON') # 每月第一個(gè)周一
五蜓萄、時(shí)序中的滑窗與分組
滑動(dòng)窗口隅茎,所謂時(shí)序的滑窗函數(shù),即把滑動(dòng)窗口用freq關(guān)鍵詞代替
import matplotlib.pyplot as plt
idx = pd.date_range('20200101', '20201231', freq='B')
np.random.seed(2020)
data = np.random.randint(-1,2,len(idx)).cumsum() # 隨機(jī)游動(dòng)構(gòu)造模擬序列
s = pd.Series(data,index=idx)
s.head()
重采樣嫉沽,重采樣對(duì)象resample和第四章中分組對(duì)象groupby的用法類(lèi)似辟犀,前者是針對(duì)時(shí)間序列的分組計(jì)算而設(shè)計(jì)的分組對(duì)象
s.resample('7min', origin='start').mean().head()
六、練習(xí)
Ex1:太陽(yáng)輻射數(shù)據(jù)集
現(xiàn)有一份關(guān)于太陽(yáng)輻射的數(shù)據(jù)集:
df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
df.head(3)
- 將
Datetime, Time
合并為一個(gè)時(shí)間列Datetime
绸硕,同時(shí)把它作為索引后排序堂竟。 - 每條記錄時(shí)間的間隔顯然并不一致,請(qǐng)解決如下問(wèn)題:
- 找出間隔時(shí)間的前三個(gè)最大值所對(duì)應(yīng)的三組時(shí)間戳玻佩。
- 是否存在一個(gè)大致的范圍出嘹,使得絕大多數(shù)的間隔時(shí)間都落在這個(gè)區(qū)間中?如果存在咬崔,請(qǐng)對(duì)此范圍內(nèi)的樣本間隔秒數(shù)畫(huà)出柱狀圖税稼,設(shè)置
bins=50
。
- 求如下指標(biāo)對(duì)應(yīng)的
Series
:
- 溫度與輻射量的6小時(shí)滑動(dòng)相關(guān)系數(shù)
- 以三點(diǎn)垮斯、九點(diǎn)郎仆、十五點(diǎn)、二十一點(diǎn)為分割兜蠕,該觀測(cè)所在時(shí)間區(qū)間的溫度均值序列
- 每個(gè)觀測(cè)6小時(shí)前的輻射量(一般而言不會(huì)恰好取到扰肌,此時(shí)取最近時(shí)間戳對(duì)應(yīng)的輻射量)
# 合并為Datetime,作為索引后排序
Datetime = pd.to_datetime(df.Data) + pd.to_timedelta(df.Time)
df_op = df.drop(['Data', 'Time'], axis=1).set_index(Datetime)
df_op.rename_axis(index='Datetime', inplace=True)
df_op.sort_values('Datetime', inplace=True)
df_op.head()
# 找出間隔時(shí)間的前三個(gè)最大值對(duì)應(yīng)的三組時(shí)間戳
Datetime = pd.Series(df_op.index)
index = Datetime.diff(1).sort_values(ascending=False).head(3).index
df_op.iloc[[index[0]-1, index[0], index[1]-1, index[1], index[2]-1, index[2]]]
delta = pd.to_timedelta(Datetime.diff(1))
delta.quantile(0.01)
cond1 = delta > delta.quantile(0.01)
cond2 = delta < delta.quantile(0.99)
delta.loc[(cond1 & cond2)].astype('timedelta64[s]').plot.hist(bins=50)
# 6小時(shí)滑動(dòng)相關(guān)系數(shù)
r1 = df_op.Radiation.rolling('6H')
r1.corr(df_op.Temperature).head(10)
# 溫度均值
df_op.Temperature.resample('6H', origin='2016-09-01 03:00:00').mean()
Ex2:水果銷(xiāo)量數(shù)據(jù)集
現(xiàn)有一份2019年每日水果銷(xiāo)量記錄表:
df = pd.read_csv('../data/fruit.csv')
df.head(3)
- 統(tǒng)計(jì)如下指標(biāo):
- 每月上半月(15號(hào)及之前)與下半月葡萄銷(xiāo)量的比值
- 每月最后一天的生梨銷(xiāo)量總和
- 每月最后一天工作日的生梨銷(xiāo)量總和
- 每月最后五天的蘋(píng)果銷(xiāo)量均值
- 按月計(jì)算周一至周日各品種水果的平均記錄條數(shù)熊杨,行索引外層為水果名稱(chēng)曙旭,內(nèi)層為月份墩剖,列索引為星期。
- 按天計(jì)算向前10個(gè)工作日窗口的蘋(píng)果銷(xiāo)量均值序列夷狰,非工作日的值用上一個(gè)工作日的結(jié)果填充岭皂。
df.Date = pd.to_datetime(df.Date)
df.set_index('Date', inplace=True)
df.head()
def process(group):
ret = pd.Series(data=np.zeros(4), index=['葡萄銷(xiāo)量比值', '最后一天梨銷(xiāo)量', '最后工作日梨銷(xiāo)量', '最后五天蘋(píng)果均值'])
# 上半月與下半月的葡萄銷(xiāo)量的比值
date = pd.to_datetime(group.index).to_series()
Grape = group.Fruit == 'Grape'
ret['葡萄銷(xiāo)量比值'] = group.loc[(date.dt.day<=15 & Grape), 'Sale'].sum() / group.loc[(date.dt.day>15 & Grape), 'Sale'].sum()
# 每月最后一天的省梨銷(xiāo)量總和
Pear = group.Fruit == 'Pear'
ret['最后一天梨銷(xiāo)量'] = group.loc[(date.dt.is_month_end & Pear), 'Sale'].sum()
# 每月最后一天工作日的省梨銷(xiāo)量總和
workday = ~(date.dt.weekday.isin([5,6]))
lastworkday = date.dt.day.loc[workday].max()
ret['最后工作日梨銷(xiāo)量'] = group.loc[((date.dt.day == lastworkday) & Pear), 'Sale'].sum()
# 每月最后五天的蘋(píng)果銷(xiāo)量均值
day = date.dt.day.unique()
day.sort()
Apple = group.Fruit == 'Apple'
ret['最后五天蘋(píng)果均值'] = group.loc[((date.dt.day.isin(day[-5:])) & Apple), 'Sale'].mean()
return ret
df.resample('1M').apply(process).head()
month_order = ['January','February','March','April','May','June','July','August','September','October','November','December']
week_order = ['Mon','Tue','Wed','Thu','Fri','Sat','Sum']
df = df.reset_index()
group1 = df.Fruit
group2 = df.Date.dt.month_name().astype('category').cat.reorder_categories(month_order, ordered=True)
group3 = df.Date.dt.dayofweek.replace(dict(zip(range(7),week_order))).astype('category').cat.reorder_categories(week_order, ordered=True)
res = df.groupby([group1, group2,group3])['Sale'].count().to_frame().unstack(2).droplevel(0,axis=1)
res.head()
df_apple = df[(df.Fruit=='Apple')&(~df.Date.dt.dayofweek.isin([5,6]))]
s = pd.Series(df_apple.Sale.values,index=df_apple.Date).groupby('Date').sum()
res = s.rolling('10D').mean().reindex(pd.date_range('20190101','20191231')).fillna(method='ffill')
res.head()