第十章 时序数据

整章知识架构

一 、时间序列中的基本对象

时间戳,即'2020-9-7 08:00:00'和'2020-9-7 10:00:00'这两个时间点分别代表了上课和下课的时刻,在pandas中称为Timestamp。同时,一系列的时间戳可以组成DatetimeIndex,而将它放到Series中后,Series的类型就变为了datetime64[ns],如果有涉及时区则为datetime64[ns, tz],其中tz是timezone的简写。

时间差,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。

时间段,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。

日期偏置,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。

data type

二、时间戳

时间戳

Timestamp的构造与属性

  • 单个时间戳:使用pd.Timestamp
ts = pd.Timestamp('2020/1/1')
ts = pd.Timestamp('2020-1-1 08:10:30')
  • 可以通过year, month, day, hour, min, second获取具体的数值

Datetime序列的生成

  • to_datetime,能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:
pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])
# 可使用format进行格式匹配
temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
  • date_range
    • start:开始时间
    • end:结束时间
    • freq:时间间隔
    • periods:时间戳个数
pd.date_range('2020-1-1','2020-2-28', freq='10D')

【练一练】

Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。

def rand_date(date_interval, size=10):
    start = pd.Timestamp(date_interval[0]).value / 10**9 # 单位转换成s
    end = pd.Timestamp(date_interval[1]).value / 10**9 # 单位转换成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'])
answer

asfreq, 对给定的序列进行类似于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]本质上可以理解为一个大整数,对于一个该类型的序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。

date  =rand_date(['2020-1-1', '2020-2-1'], size=100) # 随机生成100个日期
print(date.max()) # 最大值
print(date.min()) # 最小值
print(date.mean()) # 平均值
answer

dt对象,如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。

  • 取出时间相关的属性:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter
  • 判断时间戳是否满足条件
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')

时间戳的切片与索引

  • 利用dt对象和布尔条件联合使用
s[(idx.is_month_start|idx.is_month_end).values].head()
  • 利用切片,常用于连续时间戳
s['2020-05':'2020-7-15']

三、时间差

时间差

Timedelta的生成

  • 单个, 通过pd.Timedelta
pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
pd.Timedelta('1 days 25 minutes') # 字符串生成
  • 序列,通过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对象,属性包括days, seconds, mircroseconds, nanoseconds,它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数

Timedelta的运算

  • 与标量的乘法运算
  • 与时间戳的加减法运算
  • 与时间差的加减法与除法运算

四、日期偏置

日期偏置

Offset对象,在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期

  • CDay,其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期
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') # 每月第一个周一

五、时序中的滑窗与分组

时序中的滑窗与分组

滑动窗口,所谓时序的滑窗函数,即把滑动窗口用freq关键词代替

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() # 随机游动构造模拟序列
s = pd.Series(data,index=idx)
s.head()

重采样,重采样对象resample和第四章中分组对象groupby的用法类似,前者是针对时间序列的分组计算而设计的分组对象

s.resample('7min', origin='start').mean().head()

六、练习

Ex1:太阳辐射数据集

现有一份关于太阳辐射的数据集:

df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
df.head(3)
data
  1. Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
  2. 每条记录时间的间隔显然并不一致,请解决如下问题:
  • 找出间隔时间的前三个最大值所对应的三组时间戳。
  • 是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50
  1. 求如下指标对应的Series
  • 温度与辐射量的6小时滑动相关系数
  • 以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
  • 每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
# 合并为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()
result1
# 找出间隔时间的前三个最大值对应的三组时间戳
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]]]
result2
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)
result3
# 6小时滑动相关系数
r1 = df_op.Radiation.rolling('6H')
r1.corr(df_op.Temperature).head(10)
result4
# 温度均值
df_op.Temperature.resample('6H', origin='2016-09-01 03:00:00').mean()
result5

Ex2:水果销量数据集

现有一份2019年每日水果销量记录表:

df = pd.read_csv('../data/fruit.csv')
df.head(3)
data
  1. 统计如下指标:
  • 每月上半月(15号及之前)与下半月葡萄销量的比值
  • 每月最后一天的生梨销量总和
  • 每月最后一天工作日的生梨销量总和
  • 每月最后五天的苹果销量均值
  1. 按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
  2. 按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
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=['葡萄销量比值', '最后一天梨销量', '最后工作日梨销量', '最后五天苹果均值'])
    # 上半月与下半月的葡萄销量的比值
    date = pd.to_datetime(group.index).to_series()
    Grape = group.Fruit == 'Grape'
    ret['葡萄销量比值'] = group.loc[(date.dt.day<=15 & Grape), 'Sale'].sum() / group.loc[(date.dt.day>15 & Grape), 'Sale'].sum()
    # 每月最后一天的省梨销量总和
    Pear = group.Fruit == 'Pear'
    ret['最后一天梨销量'] = group.loc[(date.dt.is_month_end & Pear), 'Sale'].sum()
    # 每月最后一天工作日的省梨销量总和
    workday = ~(date.dt.weekday.isin([5,6]))
    lastworkday = date.dt.day.loc[workday].max()
    ret['最后工作日梨销量'] = group.loc[((date.dt.day == lastworkday)  & Pear), 'Sale'].sum()
    # 每月最后五天的苹果销量均值
    day = date.dt.day.unique()
    day.sort()
    Apple = group.Fruit == 'Apple'
    ret['最后五天苹果均值'] = group.loc[((date.dt.day.isin(day[-5:]))  & Apple), 'Sale'].mean()
    return ret
df.resample('1M').apply(process).head()
result1
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()
result2
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()
result3
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容