查看原文
其他

时间序列 | pandas时间序列基础

云朵 数据STUDIO 2022-04-28

时间序列(time series)数据是一种重要的结构化数据形式,应用于多个领域,包括金融学、经济学、生态学、神经科学、物理学等。在多个时间点观察或测量到的任何事物都可以形成一段时间序列。很多时间序列是固定频率的,也就是说,数据点是根据某种规律定期出现的(比如每15秒、每5分钟、每月出现一次)。时间序列也可以是不定期的,没有固定的时间单位或单位之间的偏移量。时间序列数据的意义取决于具体的应用场景,主要有以下几种:

  • 时间戳(timestamp),特定的时刻。

  • 固定时期(period),如2008年1月或2020年全年。

  • 时间间隔(interval),由起始和结束时间戳表示。时期(period)可以被看做间隔(interval)的特例。

本文内容包括,索引、选取、子集构造,日期的范围、频率以及移动基础等。

首先回顾下datetime模块

>>> from datetime import datetime
>>> dates = [datetime(201112), datetime(201115),
...          datetime(201117), datetime(201118),
...          datetime(2011110), datetime(2011112)]
>>> ts = pd.Series(np.random.randn(6),index = dates)
>>> ts
2011-01-02   -0.162712
2011-01-05    1.876604
2011-01-07   -0.016393
2011-01-08    0.239276
2011-01-10   -0.704732
2011-01-12   -1.502936
dtype: float64
  
# 这些datetime对象实际上是被放在一个DatetimeIndex中
>>> ts.index 
DatetimeIndex(['2011-01-02''2011-01-05''2011-01-07'
               '2011-01-08','2011-01-10''2011-01-12']
              ,dtype='datetime64[ns]', freq=None)

# DatetimeIndex中的各个标量值是pandas的Timestamp对象
>>> stamp = ts.index[0
>>> stamp
Timestamp('2011-01-02 00:00:00')

索引、选取、子集构造

根据标签索引

>>> stamp = ts.index[2]
>>> stamp
Timestamp('2011-01-07 00:00:00')

传入一个可以被解释为日期的字符串

>>> ts['1/10/2011']
-0.7047322514407551
>>> ts['20110110']
-0.7047322514407551

对于较长的时间序列,只需传入“年”或“年月”即可轻松选取数据的切片

>>> long_ts = pd.Series(np.random.randn(1000)
                        ,index = pd.date_range('01/01/2000'
                                               ,periods = 1000))
>>> long_ts
2000-01-01    1.054099
2000-01-02   -1.554446
2000-01-03    0.576552
2000-01-04    0.785388
2000-01-05   -1.674210
                ...   
2002-09-22   -0.836620
2002-09-23    0.561945
2002-09-24   -0.196348
2002-09-25   -0.937816
2002-09-26   -0.431839
Freq: D, Length: 1000, dtype: float64
            
>>> long_ts['2002']
2002-01-01   -1.084543
2002-01-02   -2.452728
2002-01-03    0.415411
2002-01-04    1.284758
2002-01-05   -0.449588
                ...   
2002-09-22   -0.836620
2002-09-23    0.561945
2002-09-24   -0.196348
2002-09-25   -0.937816
2002-09-26   -0.431839
Freq: D, Length: 269, dtype: float64
            
>>> long_ts['2002-08']
2002-08-01    1.061041
2002-08-02    0.181386
2002-08-03    0.697954
2002-08-04    0.729076
2002-08-05   -0.573171
                ...   
2002-08-27   -1.240861
2002-08-28    0.397729
2002-08-29   -2.765702
2002-08-30   -2.228761
2002-08-31   -0.386724
Freq: D, dtype: float64

datetime对象也可以进行切

>>> long_ts[datetime(2001,5,10)::100]
2001-05-10   -1.412274
2001-08-18   -0.051782
2001-11-26   -0.948275
2002-03-06    0.565756
2002-06-14    0.040260
2002-09-22   -0.836620
Freq: 100D, dtype: float64
        
>>> '''由于大部分时间序列数据都是按照时间先后排序的,
... 因此你也可以用不存在于该时间序列中的时间戳对其进行切片(即范围查询)'''

>>> ts['1/6/2011':'1/11/2011'
2011-01-07   -0.016393
2011-01-08    0.239276
2011-01-10   -0.704732
dtype: float64

truncate()等价的实例方法也可以截取两个日期之间TimeSeries:

>>> ts.truncate(after = '1/9/2011')
2011-01-02   -0.162712
2011-01-05    1.876604
2011-01-07   -0.016393
2011-01-08    0.239276
dtype: float64

对DataFrame的行进行索引

>>> dates = pd.date_range('2/1/2020', periods = 10, freq = 'W-WED')
>>> dates_1 = pd.date_range('2/1/2020', periods = 10, freq = 'W-SUN')
>>> dates
DatetimeIndex(['2020-02-05''2020-02-12''2020-02-19''2020-02-26',
               '2020-03-04''2020-03-11''2020-03-18''2020-03-25',
               '2020-04-01''2020-04-08'],
              dtype='datetime64[ns]', freq='W-WED')
>>> dates_1
DatetimeIndex(['2020-02-02''2020-02-09''2020-02-16''2020-02-23',
               '2020-03-01''2020-03-08''2020-03-15''2020-03-22',
               '2020-03-29''2020-04-05'],
              dtype='datetime64[ns]', freq='W-SUN')

>>> long_df = pd.DataFrame(np.random.randn(104)
                           , index=dates
                           , columns=['Colorado''Texas','New York''Ohio'])
>>> long_df

ColoradoTexasNew YorkNew YorkOhioOhio
2020-02-02-0.0298010.514341-0.1416551.715595
2020-02-09-0.824540-0.296288-0.8278770.245774
2020-02-16-0.0844840.1261710.423891-1.709223
2020-02-23-1.6882801.032951-0.062944-0.521174
2020-03-010.3221670.2262180.5157360.098784
2020-03-082.1176171.840786-0.283062-1.414009
2020-03-15-0.8538290.1015250.395773-0.938408
2020-03-220.022767-1.7618230.361345-1.015794
2020-03-29-1.075985-0.4035492.053879-0.075490
2020-04-05-0.4687591.5916930.0124941.013237

同样只需传入“年”或“年月”即可轻松选取数据的切片

long_df.loc['3-2020']

ColoradoTexasNew YorkOhio
2020-03-01-0.983091-0.3593750.0146951.123173
2020-03-080.506222-0.188421-1.298069-1.769189
2020-03-15-1.773529-1.6648370.0456040.365199
2020-03-22-2.059035-1.3867870.402163-1.967558
2020-03-29-1.254208-0.272317-1.5955321.349384

带有重复索引的时间序列

>>> dates = pd.DatetimeIndex(['1/1/2000''1/2/2000''1/2/2000','1/2/2000''1/3/2000'])
>>> dup_ts = pd.Series(np.arange(5), index = dates)
>>> dup_ts
2000-01-01    0
2000-01-02    1
2000-01-02    2
2000-01-02    3
2000-01-03    4
dtype: int64
    
>>> dup_ts.index.is_unique
False
>>> dup_ts['1/2/2000']
2000-01-02    1
2000-01-02    2
2000-01-02    3
dtype: int64
    
>>> """假设你想要对具有非唯一时间戳的数据进行聚合。
... 一个办法是使用groupby,并传入level=0 """

>>> group = dup_ts.groupby(level=0)
>>> group.mean()
2000-01-01    0
2000-01-02    2
2000-01-03    4
dtype: int64

日期的范围、频率以及移动

pandas中的原生时间序列一般被认为是不规则的,也就是说,它们没有固定的频率。对于大部分应用程序而言,这是无所谓的。但是,它常常需要以某种相对固定 的频率进行分析,比如每日、每月、每15分钟等(这样自然会在时间序列中引入缺失值)。幸运的是,pandas有一整套标准时间序列频率以及用于重采样、频率推断、生成固定频率日期范围的工具。例如,我们可以将之前那个时间序列转换为一 个具有固定频率(每日)的时间序列,只需调用resample即可


pandas.date_range() 生成日期范围

  • pandas.date_range可用于根据指定的频率生成指定长度的DatetimeIndex
  • 默认情况下,date_range会产生按天计算的时间点。
  • 如果只传入起始或结束日期,那就还得传入一个表示一段时间的数字,起始和结束日期定义了日期索引的严格边界
>>> pd.date_range(start='2012-04-01', periods=20)
DatetimeIndex(['2012-04-01''2012-04-02''2012-04-03''2012-04-04',
               '2012-04-05''2012-04-06''2012-04-07''2012-04-08',
               '2012-04-09''2012-04-10''2012-04-11''2012-04-12',
               '2012-04-13''2012-04-14''2012-04-15''2012-04-16',
               '2012-04-17''2012-04-18''2012-04-19''2012-04-20'],
              dtype='datetime64[ns]', freq='D')

>>> pd.date_range(end ='2012-04-01', periods=20)
DatetimeIndex(['2012-03-13''2012-03-14''2012-03-15''2012-03-16',
               '2012-03-17''2012-03-18''2012-03-19''2012-03-20',
               '2012-03-21''2012-03-22''2012-03-23''2012-03-24',
               '2012-03-25''2012-03-26''2012-03-27''2012-03-28',
               '2012-03-29''2012-03-30''2012-03-31''2012-04-01'],
              dtype='datetime64[ns]', freq='D')

如果你想要生成一个由每月最后一个工作日组成的日期索引,可以传入"BM"频率(表示business end of month,下表是频率列表),这样就只会包含时间间隔内(或刚好在边界上的)符合频率要求的日期:

别名便宜量类型说明
DDay每日历日
BBusinessDay每工作日
HHour每小时
T 或 minMinute每分
SSecond每秒
L或msMilli每毫秒(即千万分之一秒)
UMicro每微秒(即百万分之一秒)
MMonthEnd每月最后一个日历日
BMBusinessMonthEnd每月最后一个工作日
MSMonthBegin每月第一个日历日
BMSBusinessMonthBegin每月第一个工作日
W-MON、W-TUE ...Week从指定的星期几(MON、TUE、WED、THU、FRI、SAT、SUN)开始算起,每周
WON-1MON、WOM-2MON...WeekOfMonth产生每月第一、第二、第三或第四周的星期即。例如,WOM-3FRI表示每月第3个星期五
Q-JAN、Q-FEB...QuarterEnd对于以指定月份(JAN、FEB、MAR、APR、MAY、JUN、JUL、AUG、SEP、OCT、NOV、EDC)结束的年度,每季度最后一个月的最后一个日历日
BQ-JAN、BQ-FEB...BusinessQuarterEnd对于以指定月份结束的年度,每季度最后一月的最后一个工作日
QS-JAN、QS-FEB...QuarterBegin对于以指定月份结束的年度、每季度最后一月的第一个日历日
BQS-JAN、BQS-FEB...BusinessQuarterBegin对于以指定月份结束的年度、每季度最后一月的第一个工作日
A-JAN、A-FEB...YearEnd每年指定月份(JAN、FEB、MAR、APR、MAY、JUN、JUL、AUG、SEP、OCT、NOV、EDC)的最后一个日历日
BA-JAN、BA-FEB...BusinessYearEnd每年指定月份的最后一个工作日
AS-JAN、As-FEBYearBegin每年指定月份的第一个日历日
BAS-JAN、BAS-FEB...BusinessYearnBegin每年指定月份的第一个工作日

有时,虽然起始和结束日期带有时间信息,但你希望产生一组被规范化 (normalize)到午夜的时间戳。normalize选项即可实现该功能:

>>> pd.date_range('2012-05-02 12:56:31', periods=5, normalize=True)
>>> DatetimeIndex(['2012-05-02''2012-05-03''2012-05-04''2012-05-05',
                   '2012-05-06'],
                  dtype='datetime64[ns]', freq='D')

频率和日期偏移量

pandas中的频率是由一个基础频率(base frequency)和一个乘数组成的。基础频 率通常以一个字符串别名表示,比如"M"表示每月,"H"表示每小时。对于每个基础 频率,都有一个被称为日期偏移量(date offset)的对象与之对应

>>> from pandas.tseries import offsets
>>> offsets.Hour()
<Hour>
# 传入一个整数即可定义偏移量的倍数:
>>> offsets.Hour(2)  
<2 * Hours>

# 大部分偏移量对象都可通过加法进行连接
>>> Hour(2) + Minute(30
<150 * Minutes>

#在创建日期范围时,给freq传入参数即可实现偏移频率
>>> pd.date_range('2000-01-01', periods=10, freq='1h30min')  
DatetimeIndex(['2000-01-01 00:00:00''2000-01-01 01:30:00',
               '2000-01-01 03:00:00''2000-01-01 04:30:00',
               '2000-01-01 06:00:00''2000-01-01 07:30:00',
               '2000-01-01 09:00:00''2000-01-01 10:30:00',
               '2000-01-01 12:00:00''2000-01-01 13:30:00'],
              dtype='datetime64[ns]', freq='90T')

WOM 日期 (Week Of Month)

WOM(Week Of Month)是一种非常实用的频率类,它以WOM开头。它使你能获得诸如“每月第3个星期五”之类的日期:

>>> rng = pd.date_range('2012-01-01''2012-09-01', freq='WOM-3FRI'# 每月第3个星期五
>>> list(rng)
[Timestamp('2012-01-20 00:00:00', freq='WOM-3FRI'),
 Timestamp('2012-02-17 00:00:00', freq='WOM-3FRI'),
 Timestamp('2012-03-16 00:00:00', freq='WOM-3FRI'),
 Timestamp('2012-04-20 00:00:00', freq='WOM-3FRI'),
 Timestamp('2012-05-18 00:00:00', freq='WOM-3FRI'),
 Timestamp('2012-06-15 00:00:00', freq='WOM-3FRI'),
 Timestamp('2012-07-20 00:00:00', freq='WOM-3FRI'),
 Timestamp('2012-08-17 00:00:00', freq='WOM-3FRI')]

shfit() -- 移动(超前和滞后)数据

移动(shifting)指的是沿着时间轴将数据前移或后移。SeriesDataFrame都有一个shift方法用于执行单纯的前移或后移操作,保持索引不变:

>>> ts
2011-01-02   -0.162712
2011-01-05    1.876604
2011-01-07   -0.016393
2011-01-08    0.239276
2011-01-10   -0.704732
2011-01-12   -1.502936
dtype: float64
  
# 往前移动2格,索引不变  
>>> ts.shift(2)  
2011-01-02         NaN
2011-01-05         NaN
2011-01-07   -0.162712
2011-01-08    1.876604
2011-01-10   -0.016393
2011-01-12    0.239276
dtype: float64
  
>>> ts.shift(-2)
2011-01-02   -0.016393
2011-01-05    0.239276
2011-01-07   -0.704732
2011-01-08   -1.502936
2011-01-10         NaN
2011-01-12         NaN
dtype: float64

shift通常用于计算一个时间序列或多个时间序列(如DataFrame的列)中的百分比变化。

>>> ts/ts.shift(1)-1
2011-01-02          NaN
2011-01-05   -12.533276
2011-01-07    -1.008735
2011-01-08   -15.596514
2011-01-10    -3.945270
2011-01-12     1.132634
dtype: float64
  
>>> ts
2011-01-02   -0.162712
2011-01-05    1.876604
2011-01-07   -0.016393
2011-01-08    0.239276
2011-01-10   -0.704732
2011-01-12   -1.502936
dtype: float64
  
>>> ts.shift(2
2011-01-02         NaN
2011-01-05         NaN
2011-01-07   -0.162712
2011-01-08    1.876604
2011-01-10   -0.016393
2011-01-12    0.239276
dtype: float64
  
>>> ts.shift(2,freq = 'D')
2011-01-04   -0.162712
2011-01-07    1.876604
2011-01-09   -0.016393
2011-01-10    0.239276
2011-01-12   -0.704732
2011-01-14   -1.502936
dtype: float64

>>> ts.shift(2, freq = 'BM')
2011-02-28   -0.162712
2011-02-28    1.876604
2011-02-28   -0.016393
2011-02-28    0.239276
2011-02-28   -0.704732
2011-02-28   -1.502936
dtype: float64

通过偏移量对日期进行位移

>>> from pandas.tseries.offsets import *
>>> now = datetime(2020,2,20)
>>> now + 3 * Day()
Timestamp('2020-02-23 00:00:00')

如果加的是锚点偏移量(比如MonthEnd),第一次增量会将原日期向前滚动到符合频率规则的下一个日期:

>>> now + MonthEnd()
Timestamp('2020-02-29 00:00:00')

通过锚点偏移量的rollforwardrollback方法,可明确地将日期向前或向后“滚动”:

>>> now
datetime.datetime(202022000)

>>> me_offset = MonthEnd()
>>> MonthEnd().rollforward(now)
Timestamp('2020-02-29 00:00:00')

>>> me_offset.rollback(now)
Timestamp('2020-01-31 00:00:00')

结合groupby运用‘滚动’

>>> ts = pd.Series(np.random.randn(20), index = pd.date_range('1/15/2020',periods = 20, freq = '5d'))>>> ts
2020-01-15    1.036827
2020-01-20    0.013990
2020-01-25    0.833330
2020-01-30    0.377223
2020-02-04   -0.275511
2020-02-09   -0.331296
2020-02-14   -0.433451
2020-02-19   -0.518679
2020-02-24    0.828722
2020-02-29    0.158464
2020-03-05    0.737144
2020-03-10    0.318404
2020-03-15   -0.240118
2020-03-20   -0.910773
2020-03-25    0.416353
2020-03-30   -1.265480
2020-04-04    0.882785
2020-04-09    2.628588
2020-04-14    0.777044
2020-04-19   -1.821877
Freq: 5D, dtype: float64
    
>>> ts.groupby(MonthEnd().rollforward).mean()
2020-01-31    0.565343
2020-02-29   -0.095292
2020-03-31   -0.157412
2020-04-30    0.616635
dtype: float64

更简单、更快速地实现该功能的办法是使用resample

>>> ts.resample('M').mean()
2020-01-31    0.565343
2020-02-29   -0.095292
2020-03-31   -0.157412
2020-04-30    0.616635
Freq: M, dtype: float64



推荐阅读

-- 数据STUDIO --

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存