Python Pandas 第9章 时序数据

import pandas as pd
import numpy as np

一、时序的创建

1. 四类时间变量

现在理解可能关于③和④有些困惑,后面会作出一些说明

名称描述元素类型创建方式
① Date times(时间点/时刻)描述特定日期或时间点Timestampto_datetime或date_range
② Time spans(时间段/时期)由时间点定义的一段时期PeriodPeriod或period_range
③ Date offsets(相对时间差)一段时间的相对大小(与夏/冬令时无关)DateOffsetDateOffset
④ Time deltas(绝对时间差)一段时间的绝对大小(与夏/冬令时有关)Timedeltato_timedelta或timedelta_range

2. 时间点的创建

(a)to_datetime方法

Pandas在时间点建立的输入格式规定上给了很大的自由度,下面的语句都能正确建立同一时间点

>>> pd.to_datetime('2020.1.1')
>>> pd.to_datetime('2020 1.1')
>>> pd.to_datetime('2020 1 1')
>>> pd.to_datetime('2020 1-1')
>>> pd.to_datetime('2020-1 1')
>>> pd.to_datetime('2020-1-1')
>>> pd.to_datetime('2020/1/1')
>>> pd.to_datetime('1.1.2020')
>>> pd.to_datetime('1.1 2020')
>>> pd.to_datetime('1 1 2020')
>>> pd.to_datetime('1 1-2020')
>>> pd.to_datetime('1-1 2020')
>>> pd.to_datetime('1-1-2020')
>>> pd.to_datetime('1/1/2020')
>>> pd.to_datetime('20200101')
>>> pd.to_datetime('2020.0101')
Timestamp('2020-01-01 00:00:00')

下面的语句都会报错

>>> #pd.to_datetime('2020\\1\\1')
>>> #pd.to_datetime('2020`1`1')
>>> #pd.to_datetime('2020.1 1')
>>> #pd.to_datetime('1 1.2020')

此时可利用format参数强制匹配

>>> pd.to_datetime('2020\\1\\1',format='%Y\\%m\\%d')
>>> pd.to_datetime('2020`1`1',format='%Y`%m`%d')
>>> pd.to_datetime('2020.1 1',format='%Y.%m %d')
>>> pd.to_datetime('1 1.2020',format='%d %m.%Y')
Timestamp('2020-01-01 00:00:00')

同时,使用列表可以将其转为时间点索引

>>> pd.Series(range(2),index=pd.to_datetime(['2020/1/1','2020/1/2']))
2020-01-01    0
2020-01-02    1
dtype: int64

>>> type(pd.to_datetime(['2020/1/1','2020/1/2']))
pandas.core.indexes.datetimes.DatetimeIndex

对于DataFrame而言,如果列已经按照时间顺序排好,则利用to_datetime可自动转换

>>> df = pd.DataFrame({'year': [2020, 2020],'month': [1, 1], 'day': [1, 2]})
>>> pd.to_datetime(df)
0   2020-01-01
1   2020-01-02
dtype: datetime64[ns]

(b)时间精度与范围限制

事实上,Timestamp的精度远远不止day,可以最小到纳秒ns

>>> pd.to_datetime('2020/1/1 00:00:00.123456789')
Timestamp('2020-01-01 00:00:00.123456789')

同时,它带来范围的代价就是只有大约584年的时间点是可用的

>>> pd.Timestamp.min
Timestamp('1677-09-21 00:12:43.145225')

>>> pd.Timestamp.max
Timestamp('2262-04-11 23:47:16.854775807')

(c)date_range方法

一般来说,start/end/periods(时间点个数)/freq(间隔方法)是该方法最重要的参数,给定了其中的3个,剩下的一个就会被确定

>>> pd.date_range(start='2020/1/1',end='2020/1/10',periods=3)
DatetimeIndex(['2020-01-01 00:00:00', '2020-01-05 12:00:00',
               '2020-01-10 00:00:00'],
              dtype='datetime64[ns]', freq=None)

>>> pd.date_range(start='2020/1/1',end='2020/1/10',freq='D')
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
               '2020-01-05', '2020-01-06', '2020-01-07', '2020-01-08',
               '2020-01-09', '2020-01-10'],
              dtype='datetime64[ns]', freq='D')

>>> pd.date_range(start='2020/1/1',periods=3,freq='D')
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03'], dtype='datetime64[ns]', freq='D')

>>> pd.date_range(end='2020/1/3',periods=3,freq='D')
DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03'], dtype='datetime64[ns]', freq='D')

其中freq参数有许多选项,下面将常用部分罗列如下,更多选项可看这里

符号D/BWM/Q/YBM/BQ/BYMS/QS/YSBMS/BQS/BYSHTS
描述日/工作日月末月/季/年末日月/季/年末工作日月/季/年初日月/季/年初工作日小时分钟
>>> pd.date_range(start='2020/1/1',periods=3,freq='T')
DatetimeIndex(['2020-01-01 00:00:00', '2020-01-01 00:01:00',
               '2020-01-01 00:02:00'],
              dtype='datetime64[ns]', freq='T')

>>> pd.date_range(start='2020/1/1',periods=3,freq='M')
DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')

>>> pd.date_range(start='2020/1/1',periods=3,freq='BYS')
DatetimeIndex(['2020-01-01', '2021-01-01', '2022-01-03'], dtype='datetime64[ns]', freq='BAS-JAN')

bdate_range是一个类似与date_range的方法,特点在于可以在自带的工作日间隔设置上,再选择weekmask参数和holidays参数
它的freq中有一个特殊的'C'/'CBM'/'CBMS'选项,表示定制,需要联合weekmask参数和holidays参数使用
例如现在需要将工作日中的周一、周二、周五3天保留,并将部分holidays剔除

>>> weekmask = 'Mon Tue Fri'
>>> holidays = [pd.Timestamp('2020/1/%s'%i) for i in range(7,13)]
>>> #注意holidays
>>> pd.bdate_range(start='2020-1-1',end='2020-1-15',freq='C',weekmask=weekmask,holidays=holidays)
DatetimeIndex(['2020-01-03', '2020-01-06', '2020-01-13', '2020-01-14'], dtype='datetime64[ns]', freq='C')

3. DateOffset对象

(a)DataOffset与Timedelta的区别

Timedelta绝对时间差的特点指无论是冬令时还是夏令时,增减1day都只计算24小时
DataOffset相对时间差指,无论一天是23\24\25小时,增减1day都与当天相同的时间保持一致
例如,英国当地时间 2020年03月29日,01:00:00 时钟向前调整 1 小时 变为 2020年03月29日,02:00:00,开始夏令时

>>> ts = pd.Timestamp('2020-3-29 01:00:00', tz='Europe/Helsinki')
>>> ts + pd.Timedelta(days=1)
Timestamp('2020-03-30 02:00:00+0300', tz='Europe/Helsinki')

>>> ts + pd.DateOffset(days=1)
Timestamp('2020-03-30 01:00:00+0300', tz='Europe/Helsinki')

这似乎有些令人头大,但只要把tz(time zone)去除就可以不用管它了,两者保持一致,除非要使用到时区变换

>>> ts = pd.Timestamp('2020-3-29 01:00:00')
>>> ts + pd.Timedelta(days=1)
Timestamp('2020-03-30 01:00:00')

>>> ts + pd.DateOffset(days=1)
Timestamp('2020-03-30 01:00:00')

(b)增减一段时间

DateOffset的可选参数包括years/months/weeks/days/hours/minutes/seconds

>>> pd.Timestamp('2020-01-01') + pd.DateOffset(minutes=20) - pd.DateOffset(weeks=2)
Timestamp('2019-12-18 00:20:00')

(c)各类常用offset对象

freqD/BW(B)M/(B)Q/(B)Y(B)MS/(B)QS/(B)YSHTSC
offsetDateOffset/BDayWeek(B)MonthEnd/(B)QuarterEnd/(B)YearEnd(B)MonthBegin/(B)QuarterBegin/(B)YearBeginHourMinuteSecondCDay(定制工作日)
>>> pd.Timestamp('2020-01-01') + pd.offsets.Week(2)
Timestamp('2020-01-15 00:00:00')

>>> pd.Timestamp('2020-01-01') + pd.offsets.BQuarterBegin(1)
Timestamp('2020-03-02 00:00:00')

(d)序列的offset操作

利用apply函数

>>> pd.Series(pd.offsets.BYearBegin(3).apply(i) for i in pd.date_range('20200101',periods=3,freq='Y'))
0   2023-01-02
1   2024-01-01
2   2025-01-01
dtype: datetime64[ns]

直接使用对象加减

>>> pd.date_range('20200101',periods=3,freq='Y') + pd.offsets.BYearBegin(3)
DatetimeIndex(['2023-01-02', '2024-01-01', '2025-01-01'], dtype='datetime64[ns]', freq='A-DEC')

定制offset,可以指定weekmaskholidays参数(思考为什么三个都是一个值)

>>> pd.Series(pd.offsets.CDay(3,weekmask='Wed Fri',holidays='2020010').apply(i)
                                  for i in pd.date_range('20200105',periods=3,freq='D'))
0   2020-01-15
1   2020-01-15
2   2020-01-15
dtype: datetime64[ns]

二、时序的索引及属性

1. 索引切片

这一部分几乎与第二章的规则完全一致

>>> rng = pd.date_range('2020','2021', freq='W')
>>> ts = pd.Series(np.random.randn(len(rng)), index=rng)
>>> ts.head()
2020-01-05   -0.275349
2020-01-12    2.359218
2020-01-19   -0.447633
2020-01-26   -0.479830
2020-02-02    0.517587
Freq: W-SUN, dtype: float64

>>> ts['2020-01-26']
-0.47982974619679947

合法字符自动转换为时间点

>>> ts['2020-01-26':'20200726'].head()
2020-01-26   -0.479830
2020-02-02    0.517587
2020-02-09   -0.575879
2020-02-16    0.952187
2020-02-23    0.554098
Freq: W-SUN, dtype: float64

2. 子集索引

>>> ts['2020-7'].head()
2020-07-05   -0.088912
2020-07-12    0.153852
2020-07-19    1.670324
2020-07-26    0.568214
Freq: W-SUN, dtype: float64

支持混合形态索引

>>> ts['2011-1':'20200726'].head()
2020-01-05   -0.275349
2020-01-12    2.359218
2020-01-19   -0.447633
2020-01-26   -0.479830
2020-02-02    0.517587
Freq: W-SUN, dtype: float64

3. 时间点的属性

采用dt对象可以轻松获得关于时间的信息

>>> pd.Series(ts.index).dt.week.head()
0    1
1    2
2    3
3    4
4    5
dtype: int64

>>> pd.Series(ts.index).dt.day.head()
0     5
1    12
2    19
3    26
4     2
dtype: int64

利用strftime可重新修改时间格式

>>> pd.Series(ts.index).dt.strftime('%Y-间隔1-%m-间隔2-%d').head()
0    2020-间隔1-01-间隔2-05
1    2020-间隔1-01-间隔2-12
2    2020-间隔1-01-间隔2-19
3    2020-间隔1-01-间隔2-26
4    2020-间隔1-02-间隔2-02
dtype: object

对于datetime对象可以直接通过属性获取信息

>>> pd.date_range('2020','2021', freq='W').month
Int64Index([ 1,  1,  1,  1,  2,  2,  2,  2,  3,  3,  3,  3,  3,  4,  4,  4,  4,
             5,  5,  5,  5,  5,  6,  6,  6,  6,  7,  7,  7,  7,  8,  8,  8,  8,
             8,  9,  9,  9,  9, 10, 10, 10, 10, 11, 11, 11, 11, 11, 12, 12, 12,
            12],
           dtype='int64')

>>> pd.date_range('2020','2021', freq='W').weekday
Int64Index([6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
            6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
            6, 6, 6, 6, 6, 6, 6, 6],
           dtype='int64')

三、重采样

所谓重采样,就是指resample函数,它可以看做时序版本的groupby函数

1. resample对象的基本操作

采样频率一般设置为上面提到的offset字符

>>> df_r = pd.DataFrame(np.random.randn(1000, 3),index=pd.date_range('1/1/2020', freq='S', periods=1000),
                  columns=['A', 'B', 'C'])
>>> r = df_r.resample('3min')
>>> r
<pandas.core.resample.DatetimeIndexResampler object at 0x7ff73ebafc10>

>>> r.sum()
                              A           B           C
2020-01-01 00:00:00   -8.772685  -27.074716    2.134617
2020-01-01 00:03:00    3.822484    8.912459	 -15.448955
2020-01-01 00:06:00    2.744722   -8.055139	 -11.364361
2020-01-01 00:09:00    4.655620  -11.524496  -10.536002
2020-01-01 00:12:00  -10.546811    5.063887   11.776490
2020-01-01 00:15:00    8.795150  -12.828809   -8.393950

>>> df_r2 = pd.DataFrame(np.random.randn(200, 3),index=pd.date_range('1/1/2020', freq='D', periods=200),
                  columns=['A', 'B', 'C'])
>>> r = df_r2.resample('CBMS')
>>> r.sum()
                    A          B          C
2020-01-01   5.278470   1.688588   5.904806
2020-02-03  -3.581797   7.515267   0.205308
2020-03-02  -5.021605  -4.441066   5.433917
2020-04-01   0.671702   3.840042   4.922487
2020-05-01   4.613352   9.702408  -4.928112
2020-06-01  -0.598191   7.387416   8.716921
2020-07-01  -0.327200  -1.577507  -3.956079

2. 采样聚合

>>> r = df_r.resample('3T')
>>> r['A'].mean()
2020-01-01 00:00:00   -0.048737
2020-01-01 00:03:00    0.021236
2020-01-01 00:06:00    0.015248
2020-01-01 00:09:00    0.025865
2020-01-01 00:12:00   -0.058593
2020-01-01 00:15:00    0.087952
Freq: 3T, Name: A, dtype: float64

>>> r['A'].agg([np.sum, np.mean, np.std])
                           sum      mean      std
2020-01-01 00:00:00  -8.772685 -0.048737 0.939954
2020-01-01 00:03:00   3.822484  0.021236 1.004048
2020-01-01 00:06:00   2.744722  0.015248 1.018865
2020-01-01 00:09:00   4.655620  0.025865 1.020881
2020-01-01 00:12:00 -10.546811 -0.058593 0.954328
2020-01-01 00:15:00   8.795150  0.087952 1.199379

类似地,可以使用函数/lambda表达式

>>> r.agg({'A': np.sum,'B': lambda x: max(x)-min(x)})
                             A         B
2020-01-01 00:00:00  -8.772685  4.950006
2020-01-01 00:03:00   3.822484  5.711679
2020-01-01 00:06:00   2.744722  6.923072
2020-01-01 00:09:00   4.655620  6.370589
2020-01-01 00:12:00 -10.546811  4.544878
2020-01-01 00:15:00   8.795150  5.244546

3. 采样组的迭代

采样组的迭代和groupby迭代完全类似,对于每一个组都可以分别做相应操作

>>> small = pd.Series(range(6),index=pd.to_datetime(['2020-01-01 00:00:00', '2020-01-01 00:30:00'
                                                 , '2020-01-01 00:31:00','2020-01-01 01:00:00'
                                                 ,'2020-01-01 03:00:00','2020-01-01 03:05:00']))
>>> resampled = small.resample('H')
>>> for name, group in resampled:
    print("Group: ", name)
    print("-" * 27)
    print(group, end="\n\n")
Group:  2020-01-01 00:00:00
---------------------------
2020-01-01 00:00:00    0
2020-01-01 00:30:00    1
2020-01-01 00:31:00    2
dtype: int64

Group:  2020-01-01 01:00:00
---------------------------
2020-01-01 01:00:00    3
dtype: int64

Group:  2020-01-01 02:00:00
---------------------------
Series([], dtype: int64)

Group:  2020-01-01 03:00:00
---------------------------
2020-01-01 03:00:00    4
2020-01-01 03:05:00    5
dtype: int64

四、窗口函数

下面主要介绍pandas中两类主要的窗口(window)函数:rolling/expanding

>>> s = pd.Series(np.random.randn(1000),index=pd.date_range('1/1/2020', periods=1000))
>>> s.head()
2020-01-01    0.305974
2020-01-02    0.185221
2020-01-03   -0.646472
2020-01-04   -1.430293
2020-01-05   -0.956094
Freq: D, dtype: float64

1. Rolling

(a)常用聚合

所谓rolling方法,就是规定一个窗口,它和groupby对象一样,本身不会进行操作,需要配合聚合函数才能计算结果

>>> s.rolling(window=50)
Rolling [window=50,center=False,axis=0]

>>> s.rolling(window=50).mean()
2020-01-01         NaN
2020-01-02         NaN
2020-01-03         NaN
2020-01-04         NaN
2020-01-05         NaN
                ...   
2022-09-22    0.160743
2022-09-23    0.136296
2022-09-24    0.147523
2022-09-25    0.133087
2022-09-26    0.130841
Freq: D, Length: 1000, dtype: float64

min_periods参数是指需要的非缺失数据点数量阀值

>>> s.rolling(window=50,min_periods=3).mean().head()
2020-01-01         NaN
2020-01-02         NaN
2020-01-03   -0.051759
2020-01-04   -0.396392
2020-01-05   -0.508333
Freq: D, dtype: float64

count/sum/mean/median/min/max/std/var/skew/kurt/quantile/cov/corr都是常用的聚合函数

(b)rolling的apply聚合

使用apply聚合时,只需记住传入的是window大小的Series,输出的必须是标量即可,比如如下计算变异系数

>>> s.rolling(window=50,min_periods=3).apply(lambda x:x.std()/x.mean()).head()
2020-01-01          NaN
2020-01-02          NaN
2020-01-03   -10.018809
2020-01-04    -2.040720
2020-01-05    -1.463460
Freq: D, dtype: float64

(c)基于时间的rolling

>>> s.rolling('15D').mean().head()
2020-01-01    0.305974
2020-01-02    0.245598
2020-01-03   -0.051759
2020-01-04   -0.396392
2020-01-05   -0.508333
Freq: D, dtype: float64

可选closed='right'(默认)\'left'\'both'\'neither'参数,决定端点的包含情况

>>> s.rolling('15D', closed='right').sum().head()
2020-01-01    0.305974
2020-01-02    0.491195
2020-01-03   -0.155277
2020-01-04   -1.585570
2020-01-05   -2.541664
Freq: D, dtype: float64

2. Expanding

(a)expanding函数

普通的expanding函数等价与rolling(window=len(s),min_periods=1),是对序列的累计计算

>>> s.rolling(window=len(s),min_periods=1).sum().head()
2020-01-01    0.305974
2020-01-02    0.491195
2020-01-03   -0.155277
2020-01-04   -1.585570
2020-01-05   -2.541664
Freq: D, dtype: float64

>>> s.expanding().sum().head()
2020-01-01    0.305974
2020-01-02    0.491195
2020-01-03   -0.155277
2020-01-04   -1.585570
2020-01-05   -2.541664
Freq: D, dtype: float64

apply方法也是同样可用的

>>> s.expanding().apply(lambda x:sum(x)).head()
2020-01-01    0.305974
2020-01-02    0.491195
2020-01-03   -0.155277
2020-01-04   -1.585570
2020-01-05   -2.541664
Freq: D, dtype: float64

(b)几个特别的Expanding类型函数

cumsum/cumprod/cummax/cummin都是特殊expanding累计计算方法

>>> s.cumsum().head()
2020-01-01    0.305974
2020-01-02    0.491195
2020-01-03   -0.155277
2020-01-04   -1.585570
2020-01-05   -2.541664
Freq: D, dtype: float64

>>> s.cumsum().head()
2020-01-01    0.305974
2020-01-02    0.491195
2020-01-03   -0.155277
2020-01-04   -1.585570
2020-01-05   -2.541664
Freq: D, dtype: float64

shift/diff/pct_change都是涉及到了元素关系
①shift是指序列索引不变,但值向后移动
②diff是指前后元素的差,period参数表示间隔,默认为1,并且可以为负
③pct_change是值前后元素的变化百分比,period参数与diff类似

>>> s.shift(2).head()
2020-01-01         NaN
2020-01-02         NaN
2020-01-03    0.305974
2020-01-04    0.185221
2020-01-05   -0.646472
Freq: D, dtype: float64

>>> s.diff(3).head()
2020-01-01         NaN
2020-01-02         NaN
2020-01-03         NaN
2020-01-04   -1.736267
2020-01-05   -1.141316
Freq: D, dtype: float64

>>> s.pct_change(3).head()
2020-01-01         NaN
2020-01-02         NaN
2020-01-03         NaN
2020-01-04   -5.674559
2020-01-05   -6.161897
Freq: D, dtype: float64

相关文章
Python Pandas 第1章 基础
Python Pandas 第2章 索引
Python Pandas 第3章 分组
Python Pandas 第4章 变形
Python Pandas 第5章 合并
Python Pandas 第6章 缺少数据
Python Pandas 第7章 文本数据
Python Pandas 第8章 分类数据
Python Pandas 第9章 时序数据

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值