datawhale pandas 打卡10 时序数据

内容简介

这次文章的内容是pandas的文本数据

时序中的基本对象

常用的对象有

概念单元素类型数组类型pandas数据类型
Date times TimestampDatetime/indexdatatime64[ns]
Time deltasTimedeltaTimedeltaIndextimedelta64[ns]
Time spansPeriodPeriodIndexperiod[freq]
Date offsets`DateOffsetNoneNone

时间戳

时间戳的创建

单个时间戳可以利用pd.Timestamp实现,常见的日期格式,如:YYYY/MM/DD, YYYY/MM/DD HH:MM:SS都可以被转换。

print(pd.Timestamp('2020/01/01'))
print(pd.Timestamp('2020/1/1 08:10:40'))

out

2020-01-01 00:00:00
2020-01-01 08:10:40

可以利用相关的参数获取时间戳某个部分的值。
分别使用year,month,day,hour,minute,second得到年月日时分秒的值。

1

时间戳的上下限

利用max和min参数得到时间戳类的上下限。

print(pd.Timestamp.max)
print(pd.Timestamp.min)
print(pd.Timestamp.max.year-pd.Timestamp.min.year)

out

2262-04-11 23:47:16.854775807
1677-09-21 00:12:43.145224193
585

Datetime序列的生成

利用to_datetime将时间戳类型转化为Datetime类型

pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])

out

DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)

当时间戳的格式为非常见格式时,使用format指定参数

temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
temp

out

DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)

dt对象

对于datetime64[ns]对象来说,大致有三类操作:取出时间相关属性,判断时间戳是否满足要求,取整操作

第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。

s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
s.dt.date

out

0    2020-01-01
1    2020-01-02
2    2020-01-03
dtype: object

第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:

s.dt.is_year_start # 还可选 is_quarter/month_start

out

0     True
1    False
2    False
dtype: bool

第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S。这个常常体现在date_range方法的freq参数中。

s = pd.Series(pd.date_range('2020-1-1 20:35:00', '2020-1-1 22:35:00', freq='45min'))
s

out

0   2020-01-01 20:35:00
1   2020-01-01 21:20:00
2   2020-01-01 22:05:00
dtype: datetime64[ns]

时间戳的切片与索引

一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,第一类方法是利用dt对象和布尔条件联合使用,另一种方式是利用切片,后者常用于连续时间戳。下面,举一些例子说明:

创建的简单数据集

s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
idx = pd.Series(s.index).dt
s.head()

out

2020-01-01    0
2020-01-02    1
2020-01-03    1
2020-01-04    0
2020-01-05    0
Freq: D, dtype: int32

取出每个月的第一天或者最后一天

s[(idx.is_month_start|idx.is_month_end).values].head()

out

2020-01-01    0
2020-01-31    1
2020-02-01    1
2020-02-29    1
2020-03-01    0
dtype: int32
s['2020-05':'2020-7-15'].head()

out

2020-05-01    1
2020-05-02    1
2020-05-03    1
2020-05-04    1
2020-05-05    1
Freq: D, dtype: int32

时间差

Timedelta的生成

正如在第一节中所说,时间差可以理解为两个时间戳的差,这里也可以通过pd.Timedelta来构造:

将这一列的时间转化为时间差对象
在这里插入图片描述

s = pd.to_timedelta(df.Time_Record)
s.head()

out

0   0 days 00:04:34
1   0 days 00:04:20
2   0 days 00:05:22
3   0 days 00:04:08
4   0 days 00:05:22
Name: Time_Record, dtype: timedelta64[ns]

对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds, nanoseconds,它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:

s.dt.seconds.head()

out

0    274
1    260
2    322
3    248
4    322
Name: Time_Record, dtype: int64

Timedelta的运算

时间差支持的常用运算有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:

td1 = pd.Timedelta(days=1)
td2 = pd.Timedelta(days=3)
ts = pd.Timestamp('20200101')
print(td1 * 2)
print(td2 - td1)
print(ts + td1)
print(ts - td1)

out

2 days 00:00:00
2 days 00:00:00
2020-01-02 00:00:00
2019-12-31 00:00:00

日期偏置

Offset对象

日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。

pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)

out

Timestamp('2020-09-07 00:00:00')
pd.Timestamp('20200907') + pd.offsets.BDay(30)

out

Timestamp('2020-10-19 00:00:00')

常用的日期偏置如下可以查阅这里的文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay,其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:

my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
dr = pd.date_range('20200108', '20200111')
dr.to_series().dt.dayofweek

out

2020-01-08    2
2020-01-09    3
2020-01-10    4
2020-01-11    5
Freq: D, dtype: int64

利用生成的过滤器处理日期

[i + my_filter for i in dr]

out

[Timestamp('2020-01-10 00:00:00'),
 Timestamp('2020-01-10 00:00:00'),
 Timestamp('2020-01-15 00:00:00'),
 Timestamp('2020-01-15 00:00:00')]

上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。

时序中的滑窗与分组

滑动窗口

所谓时序的滑窗函数,即把滑动窗口用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:

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

out

2020-01-01   -1
2020-01-02   -2
2020-01-03   -1
2020-01-06   -1
2020-01-07   -2
Freq: B, dtype: int32

画图

r = s.rolling('30D')
plt.plot(s)
plt.title('BOLL LINES')
plt.plot(r.mean())
plt.plot(r.mean()+r.std()*2)
plt.plot(r.mean()-r.std()*2)

out
在这里插入图片描述

2. 重采样

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

例如,对上面的序列计算每10天的均值:

s.resample('10D').mean().head()

out

2020-01-01   -2.000000
2020-01-11   -3.166667
2020-01-21   -3.625000
2020-01-31   -4.000000
2020-02-10   -0.375000
Freq: 10D, dtype: float64

练习

Ex1:太阳辐射数据集

现有一份关于太阳辐射的数据集:
在这里插入图片描述
将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。

df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
df['Datetime']=pd.to_datetime((df.Data.str.replace('12:00:00 AM','')+df.Time))
df=df.set_index('Datetime').sort_index()
df.drop(['Data','Time'],inplace=True,axis=1)
df

out

DatetimeRadiationTemperature
2016-09-01 00:00:082.5851
2016-09-01 00:05:102.8351
2016-09-01 00:20:062.1651
2016-09-01 00:25:052.2151
2016-09-01 00:30:092.2551

每条记录时间的间隔显然并不一致
请解决如下问题:
找出间隔时间的前三个最大值所对应的三组时间戳。

s=df.index.to_series().diff().reset_index(drop=True).dt.total_seconds()
max_3=s.nlargest(3).index
df.index[max_3.union(max_3-1)]

out

DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',
               '2016-11-29 19:05:02', '2016-12-01 00:00:02',
               '2016-12-05 20:45:53', '2016-12-08 11:10:42'],
              dtype='datetime64[ns]', name='Datetime', freq=None)

是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。

res = s.mask((s>s.quantile(0.99))|(s<s.quantile(0.01)))
_ = plt.hist(res, bins=50)

out
在这里插入图片描述

Ex2:水果销量数据集

现有一份2019年每日水果销量记录表:
在这里插入图片描述
每月上半月(15号及之前)与下半月葡萄销量的比值

df = pd.read_csv('../data/fruit.csv')

df.Date = pd.to_datetime(df.Date)

df_grape = df.query("Fruit == 'Grape'")

res = df_grape.groupby([np.where(df_grape.Date.dt.day<=15,'First', 'Second'),df_grape.Date.dt.month])['Sale'].mean().to_frame().unstack(0).droplevel(0,axis=1)
# [np.where(df_grape.Date.dt.day<=15,'First', 'Second'),df_grape.Date.dt.month] 构造上半月和下半月的标记以及月份的标记,并将其作为分组的依据
res

out

DateFirstSecond
166.349556.4677
259.447161.3558
357.502960.4434
460.437859.2065
557.135661.3661

每月最后一天的生梨销量总和

df = pd.read_csv('../data/fruit.csv',parse_dates=['Date'])
df[df.Date.dt.is_month_end].query('Fruit=="Pear"').set_index('Date').resample('M')['Sale'].sum()

out

Date
2019-01-31    847
2019-02-28    774
2019-03-31    761
2019-04-30    648
2019-05-31    616
2019-06-30    179
2019-07-31    757
2019-08-31    813
2019-09-30    858
2019-10-31    753
2019-11-30    859
Freq: M, Name: Sale, dtype: int64

每月最后一天工作日的生梨销量总和

df = pd.read_csv('../data/fruit.csv',parse_dates=['Date'])
df[df.Date.isin(pd.date_range('20190101', '20191231',
                freq='BM'))].query("Fruit == 'Pear'"
                ).groupby('Date').Sale.sum()

out

df = pd.read_csv('../data/fruit.csv',parse_dates=['Date'])
df[df.Date.isin(pd.date_range('20190101', '20191231',
                freq='BM'))].query("Fruit == 'Pear'"
                ).groupby('Date').Sale.sum()
df = pd.read_csv('../data/fruit.csv',parse_dates=['Date'])
df[df.Date.isin(pd.date_range('20190101', '20191231',
                freq='BM'))].query("Fruit == 'Pear'"
                ).groupby('Date').Sale.sum()
Date
2019-01-31     847
2019-02-28     774
2019-03-29     510
2019-04-30     648
2019-05-31     616
2019-06-28     605
2019-07-31     757
2019-08-30     502
2019-09-30     858
2019-10-31     753
2019-11-29    1193
Name: Sale, dtype: int64

每月最后五天的苹果销量均值

target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
            ).dt.month)['Date'].nlargest(5).reset_index(drop=True)


res = df.set_index('Date').loc[target_dt].reset_index(
            ).query("Fruit == 'Apple'")


res = res.groupby(res.Date.dt.month)['Sale'].mean(
            ).rename_axis('Month')
res

out

Month
1     65.313725
2     54.061538
3     59.325581
4     65.795455
5     57.465116
6     61.897436
7     57.000000
8     73.636364
9     62.301887
10    59.562500
11    64.437500
12    66.020000
Name: Sale, dtype: float64

按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。

month_order = ['January','February','March','April',
                'May','June','July','August','September',
                'October','November','December']


week_order = ['Mon','Tue','Wed','Thu','Fri','Sat','Sum']

group1 = df.Date.dt.month_name().astype('category').cat.reorder_categories(
        month_order, ordered=True)


group2 = df.Fruit

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(0).droplevel(0,axis=1)


res.head()

out
在这里插入图片描述
按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。

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

out

2019-01-01    189.000000
2019-01-02    335.500000
2019-01-03    520.333333
2019-01-04    527.750000
2019-01-05    527.750000
Freq: D, dtype: float64

总结

本次学习了pandas中时序数据的知识,以不同作用的数据结构为脉络,了解了时间戳,时间差,特殊时间差日期偏执三类数据结构,同时对数据日期的滑窗,分组进行了了解学习。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值